Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Introducing IPageModelActivatorProvider #5712

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Introducing IPageModelActivatorProvider
Fixes #5480
  • Loading branch information
pranavkm committed Jan 25, 2017
commit 936569dad0b8ab3ad3d4a5ea3faa2cf67f567e17
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Microsoft.AspNetCore.Mvc.RazorPages
{
/// <summary>
/// Provides methods to create a Razor page model.
/// </summary>
public interface IPageModelActivatorProvider
{
/// <summary>
/// Creates a Razor page model activator.
/// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The delegate used to activate the page model.</returns>
Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor descriptor);

/// <summary>
/// Releases a Razor page model.
/// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The delegate used to dispose the activated page model.</returns>
Action<PageContext, object> CreateReleaser(CompiledPageActionDescriptor descriptor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Microsoft.AspNetCore.Mvc.RazorPages
{
/// <summary>
/// Provides methods for creation and disposal of Razor page models.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The P should be capitalized because it's a proper name #JustEilonThings

/// </summary>
public interface IPageModelFactoryProvider
{
/// <summary>
/// Creates a factory for producing models for Razor pages given the specified <see cref="PageContext"/>.
/// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The Razor page model factory.</returns>
Func<PageContext, object> CreateModelFactory(CompiledPageActionDescriptor descriptor);

/// <summary>
/// Releases a Razor page model.
/// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The delegate used to release the created page model.</returns>
Action<PageContext, object> CreateModelDisposer(CompiledPageActionDescriptor descriptor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have a .Infrastructure namespace already parked it would be good to put this there and document it for extensibility. It was common in the past to subclass our factories.

{
/// <summary>
/// <see cref="IPageActivatorProvider"/> that uses type activation to create Pages.
/// </summary>
public class DefaultPageModelActivatorProvider : IPageModelActivatorProvider
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll also need the equivalent plumbing for the DI versions of these. Please open an issue for that or do it right away 👍

{
private readonly Action<PageContext, object> _disposer = Dispose;

/// <inheritdoc />
public virtual Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
{
throw new ArgumentNullException(nameof(actionDescriptor));
}

var modelTypeInfo = actionDescriptor.ModelTypeInfo?.AsType();
if (modelTypeInfo == null)
{
throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
nameof(actionDescriptor.ModelTypeInfo),
nameof(actionDescriptor)),
nameof(actionDescriptor));
}

var factory = ActivatorUtilities.CreateFactory(modelTypeInfo, Type.EmptyTypes);
return (context) => factory(context.HttpContext.RequestServices, EmptyArray<object>.Instance);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be dope to put this part into a virtual method, then there's less boilerplate if you want to use something other than ActivatorUtilities.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, my brain is crazy. Ignore this comment as soon as possible

}

public virtual Action<PageContext, object> CreateReleaser(CompiledPageActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
{
throw new ArgumentNullException(nameof(actionDescriptor));
}

if (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(actionDescriptor.ModelTypeInfo))
{
return _disposer;
}

return null;
}

private static void Dispose(PageContext context, object page)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

if (page == null)
{
throw new ArgumentNullException(nameof(page));
}

((IDisposable)page).Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Reflection;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class DefaultPageModelFactoryProvider : IPageModelFactoryProvider
{
private static readonly Func<PropertyInfo, PropertyActivator<PageContext>> _createActivateInfo =
CreateActivateInfo;
private readonly IPageModelActivatorProvider _modelActivator;

public DefaultPageModelFactoryProvider(IPageModelActivatorProvider modelActivator)
{
_modelActivator = modelActivator;
}

public virtual Func<PageContext, object> CreateModelFactory(CompiledPageActionDescriptor descriptor)
{
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}

if (descriptor.ModelTypeInfo == null)
{
return null;
}

var modelActivator = _modelActivator.CreateActivator(descriptor);
var propertyActivator = PropertyActivator<PageContext>.GetPropertiesToActivate(
descriptor.ModelTypeInfo.AsType(),
typeof(PageContextAttribute),
_createActivateInfo,
includeNonPublic: false);

return pageContext =>
{
var model = modelActivator(pageContext);
for (var i = 0; i < propertyActivator.Length; i++)
{
propertyActivator[i].Activate(model, pageContext);
}

return model;
};
}

public virtual Action<PageContext, object> CreateModelDisposer(CompiledPageActionDescriptor descriptor)
{
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}

if (descriptor.ModelTypeInfo == null)
{
return null;
}

return _modelActivator.CreateReleaser(descriptor);
}

private static PropertyActivator<PageContext> CreateActivateInfo(PropertyInfo property) =>
new PropertyActivator<PageContext>(property, pageContext => pageContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class PageActionInvokerProvider : IActionInvokerProvider
private const string ModelPropertyName = "Model";
private readonly IPageLoader _loader;
private readonly IPageFactoryProvider _pageFactoryProvider;
private readonly IPageModelFactoryProvider _modelFactoryProvider;
private readonly IActionDescriptorCollectionProvider _collectionProvider;
private readonly IFilterProvider[] _filterProviders;
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
Expand All @@ -38,6 +39,7 @@ public class PageActionInvokerProvider : IActionInvokerProvider
public PageActionInvokerProvider(
IPageLoader loader,
IPageFactoryProvider pageFactoryProvider,
IPageModelFactoryProvider modelFactoryProvider,
IActionDescriptorCollectionProvider collectionProvider,
IEnumerable<IFilterProvider> filterProviders,
IEnumerable<IValueProviderFactory> valueProviderFactories,
Expand All @@ -49,8 +51,9 @@ public PageActionInvokerProvider(
ILoggerFactory loggerFactory)
{
_loader = loader;
_collectionProvider = collectionProvider;
_pageFactoryProvider = pageFactoryProvider;
_modelFactoryProvider = modelFactoryProvider;
_collectionProvider = collectionProvider;
_filterProviders = filterProviders.ToArray();
_valueProviderFactories = valueProviderFactories.ToArray();
_modelMetadataProvider = modelMetadataProvider;
Expand Down Expand Up @@ -157,12 +160,23 @@ private PageActionInvokerCacheEntry CreateCacheEntry(
PageTypeInfo = compiledType,
};

var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor);
var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor);

Func<PageContext, object> modelFactory = null;
Action<PageContext, object> modelReleaser = null;
if (modelType != null)
{
modelFactory = _modelFactoryProvider.CreateModelFactory(compiledActionDescriptor);
modelReleaser = _modelFactoryProvider.CreateModelDisposer(compiledActionDescriptor);
}

return new PageActionInvokerCacheEntry(
compiledActionDescriptor,
_pageFactoryProvider.CreatePageFactory(compiledActionDescriptor),
_pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor),
c => { throw new NotImplementedException(); },
(_, __) => { throw new NotImplementedException(); },
pageFactory,
pageDisposer,
modelFactory,
modelReleaser,
cachedFilters);
}

Expand Down
16 changes: 16 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Microsoft.AspNetCore.Mvc.RazorPages
{
/// <summary>
/// Specifies that a Razor Page model property should be set with the current <see cref="PageContext"/> when creating
/// the model instance. The property must have a public set method.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class PageContextAttribute : Attribute
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this go in .Infrastructure? Do whatever we did with ActionContextAttribute.

{
}
}
Loading