Areas feature - Phase I

1. Areas defined by the Area attribute
2. Areas are a routeconstraint on the actiondescriptor
3. Areas find pages through route values

Other changes:
1. Remove Path from ActionDescriptor - It doesn't make sense with this change
2. Add sample Area
This commit is contained in:
Yishai Galatzer 2014-03-17 23:19:34 -07:00
parent 072e2cc1f1
commit cfb06c0de3
14 changed files with 188 additions and 21 deletions

View File

@ -0,0 +1,13 @@
using Microsoft.AspNet.Mvc;
namespace MvcSample.Web
{
[Area("Travel")]
public class Flight : Controller
{
public IActionResult Fly()
{
return View();
}
}
}

View File

@ -0,0 +1,33 @@
@using MvcSample.Web.Models
@model User
@{
Layout = "/Views/Shared/_Layout.cshtml";
ViewBag.Title = "This is the FLY action";
}
<div class="jumbotron">
<h1>ASP.NET</h1>
<p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
<p><a href="http://asp.net" class="btn btn-primary btn-large">Learn more &raquo;</a></p>
</div>
<div class="row">
<div class="col-md-4">
<h2>Getting started</h2>
<p>
ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that
enables a clean separation of concerns and gives you full control over markup
for enjoyable, agile development.
</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301865">Learn more &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Get more libraries</h2>
<p>NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301866">Learn more &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Web Hosting</h2>
<p>You can easily find a web hosting company that offers the right mix of features and price for your applications.</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301867">Learn more &raquo;</a></p>
</div>
</div>

View File

@ -31,6 +31,9 @@ namespace MvcSample.Web
DefaultHandler = new MvcApplication(serviceProvider),
};
// TODO: Add support for route constraints, so we can potentially constrain by existing routes
routes.MapRoute("{area}/{controller}/{action}");
routes.MapRoute(
"{controller}/{action}",
new { controller = "Home", action = "Index" });

View File

@ -27,7 +27,10 @@
@RenderBody()
<hr />
<address>
@Model.Address
@if (@Model != null)
{
@Model.Address
}
</address>
<footer>
<p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>

View File

@ -3,11 +3,8 @@ using System.Diagnostics;
namespace Microsoft.AspNet.Mvc
{
[DebuggerDisplay("{Path}:{Name}")]
public class ActionDescriptor
{
public virtual string Path { get; set; }
public virtual string Name { get; set; }
public List<RouteDataActionConstraint> RouteConstraints { get; set; }

View File

@ -0,0 +1,17 @@
using System;
namespace Microsoft.AspNet.Mvc
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AreaAttribute : RouteConstraintAttribute
{
public AreaAttribute(string areaName)
: base("area", areaName, blockNonAttributedActions: true)
{
if (string.IsNullOrEmpty(areaName))
{
throw new ArgumentException("Area name must not be empty", "areaName");
}
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc
{
public class ReflectedRouteConstraintsActionDescriptorProvider : IActionDescriptorProvider
{
public int Order
{
get { return ReflectedActionDescriptorProvider.DefaultOrder + 100; }
}
public void Invoke([NotNull]ActionDescriptorProviderContext context, Action callNext)
{
var removalConstraints = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// Iterate all the Reflected Action Descriptor providers and add area or other route constraints
if (context.Results != null)
{
foreach (var actionDescriptor in context.Results.OfType<ReflectedActionDescriptor>())
{
var routeConstraints = actionDescriptor.
ControllerDescriptor.
ControllerTypeInfo.
GetCustomAttributes<RouteConstraintAttribute>().
ToArray();
foreach (var routeConstraint in routeConstraints)
{
if (routeConstraint.BlockNonAttributedActions)
{
removalConstraints.Add(routeConstraint.RouteKey);
}
// Skip on duplicates
if (!ContainsKey(actionDescriptor, routeConstraint.RouteKey))
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
routeConstraint.RouteKey, routeConstraint.RouteValue));
}
}
}
foreach (var actionDescriptor in context.Results.OfType<ReflectedActionDescriptor>())
{
foreach (var key in removalConstraints)
{
if (!ContainsKey(actionDescriptor, key))
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(key, RouteKeyHandling.DenyKey));
}
}
}
}
callNext();
}
private bool ContainsKey(ActionDescriptor actionDescript, string routeKey)
{
return actionDescript.RouteConstraints.Any(rc => string.Equals(rc.RouteKey, routeKey, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@ -4,6 +4,7 @@ using System.Reflection;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.DependencyInjection;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Mvc.Rendering;
namespace Microsoft.AspNet.Mvc

View File

@ -7,18 +7,6 @@ namespace Microsoft.AspNet.Mvc
[DebuggerDisplay("CA {Path}:{Name}(RC-{RouteConstraints.Count})")]
public class ReflectedActionDescriptor : ActionDescriptor
{
public override string Path
{
get
{
return ControllerDescriptor.Name;
}
set
{
throw new InvalidOperationException("Cannot override path");
}
}
public string ControllerName
{
get

View File

@ -7,6 +7,8 @@ namespace Microsoft.AspNet.Mvc
{
public class ReflectedActionDescriptorProvider : IActionDescriptorProvider
{
public static readonly int DefaultOrder = 0;
private readonly IControllerAssemblyProvider _controllerAssemblyProvider;
private readonly IActionDiscoveryConventions _conventions;
private readonly IControllerDescriptorFactory _controllerDescriptorFactory;
@ -30,7 +32,7 @@ namespace Microsoft.AspNet.Mvc
public int Order
{
get { return 0; }
get { return DefaultOrder; }
}
public void Invoke(ActionDescriptorProviderContext context, Action callNext)

View File

@ -0,0 +1,19 @@
using System;
namespace Microsoft.AspNet.Mvc
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class RouteConstraintAttribute : Attribute
{
protected RouteConstraintAttribute([NotNull]string routeKey, [NotNull]string routeValue, bool blockNonAttributedActions)
{
RouteKey = routeKey;
RouteValue = routeValue;
BlockNonAttributedActions = blockNonAttributedActions;
}
public string RouteKey { get; private set; }
public string RouteValue { get; private set; }
public bool BlockNonAttributedActions { get; private set; }
}
}

View File

@ -0,0 +1,21 @@
using Microsoft.AspNet.Mvc;
namespace System.Collections.Generic
{
internal static class DictionaryExtensions
{
public static T GetValueOrDefault<T>([NotNull] this IDictionary<string, object> dictionary, [NotNull] string key)
{
object valueAsObject;
if (dictionary.TryGetValue(key, out valueAsObject))
{
if (valueAsObject is T)
{
return (T)valueAsObject;
}
}
return default(T);
}
}
}

View File

@ -10,6 +10,8 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private static readonly string[] _viewLocationFormats =
{
"/Areas/{2}/Views/{1}/{0}.cshtml",
"/Areas/{2}/Views/Shared/{0}.cshtml",
"/Views/{1}/{0}.cshtml",
"/Views/Shared/{0}.cshtml",
};
@ -30,8 +32,6 @@ namespace Microsoft.AspNet.Mvc.Razor
{
var actionContext = (ActionContext)context;
// TODO: We plan to change this on the next CR, so we don't have a strong depenedency directly on the specific
// type of the action descriptor
var actionDescriptor = actionContext.ActionDescriptor;
if (actionDescriptor == null)
@ -59,11 +59,13 @@ namespace Microsoft.AspNet.Mvc.Razor
}
else
{
var controllerName = actionDescriptor.Path;
var controllerName = actionContext.RouteValues.GetValueOrDefault<string>("controller");
var areaName = actionContext.RouteValues.GetValueOrDefault<string>("area");
var searchedLocations = new List<string>(_viewLocationFormats.Length);
for (int i = 0; i < _viewLocationFormats.Length; i++)
{
string path = String.Format(CultureInfo.InvariantCulture, _viewLocationFormats[i], viewName, controllerName);
string path = String.Format(CultureInfo.InvariantCulture, _viewLocationFormats[i], viewName, controllerName, areaName);
IView view = await _virtualPathFactory.CreateInstance(path);
if (view != null)
{

View File

@ -41,6 +41,8 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
ReflectedActionDescriptorProvider>();
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
ReflectedRouteConstraintsActionDescriptorProvider>();
yield return describe.Transient<INestedProvider<ActionInvokerProviderContext>,
ReflectedActionInvokerProvider>();