Merge branch 'merge/release/2.2-to-master'
# Conflicts: # build/dependencies.props # korebuild-lock.txt
This commit is contained in:
commit
5a7a3bfcb3
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
_mvcOptions = mvcOptions;
|
||||
}
|
||||
|
||||
public IList<ApiResponseType> GetApiResponseTypes(ControllerActionDescriptor action)
|
||||
public ICollection<ApiResponseType> GetApiResponseTypes(ControllerActionDescriptor action)
|
||||
{
|
||||
// We only provide response info if we can figure out a type that is a user-data type.
|
||||
// Void /Task object/IActionResult will result in no data.
|
||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
var runtimeReturnType = GetRuntimeReturnType(declaredReturnType);
|
||||
|
||||
var responseMetadataAttributes = GetResponseMetadataAttributes(action);
|
||||
if (responseMetadataAttributes.Count == 0 &&
|
||||
if (responseMetadataAttributes.Count == 0 &&
|
||||
action.Properties.TryGetValue(typeof(ApiConventionResult), out var result))
|
||||
{
|
||||
// Action does not have any conventions. Use conventions on it if present.
|
||||
|
|
@ -67,14 +67,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
.ToArray();
|
||||
}
|
||||
|
||||
private IList<ApiResponseType> GetApiResponseTypes(
|
||||
private ICollection<ApiResponseType> GetApiResponseTypes(
|
||||
IReadOnlyList<IApiResponseMetadataProvider> responseMetadataAttributes,
|
||||
Type type)
|
||||
{
|
||||
var results = new List<ApiResponseType>();
|
||||
|
||||
// Build list of all possible return types (and status codes) for an action.
|
||||
var objectTypes = new Dictionary<int, Type>();
|
||||
var results = new Dictionary<int, ApiResponseType>();
|
||||
|
||||
// Get the content type that the action explicitly set to support.
|
||||
// Walk through all 'filter' attributes in order, and allow each one to see or override
|
||||
|
|
@ -86,7 +83,17 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
{
|
||||
metadataAttribute.SetContentTypes(contentTypes);
|
||||
|
||||
if (metadataAttribute.Type == typeof(void) &&
|
||||
ApiResponseType apiResponseType;
|
||||
|
||||
if (metadataAttribute is IApiDefaultResponseMetadataProvider)
|
||||
{
|
||||
apiResponseType = new ApiResponseType
|
||||
{
|
||||
IsDefaultResponse = true,
|
||||
Type = metadataAttribute.Type,
|
||||
};
|
||||
}
|
||||
else if (metadataAttribute.Type == typeof(void) &&
|
||||
type != null &&
|
||||
(metadataAttribute.StatusCode == StatusCodes.Status200OK || metadataAttribute.StatusCode == StatusCodes.Status201Created))
|
||||
{
|
||||
|
|
@ -94,20 +101,38 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
// In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a
|
||||
// [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred
|
||||
// from the return type.
|
||||
objectTypes[metadataAttribute.StatusCode] = type;
|
||||
apiResponseType = new ApiResponseType
|
||||
{
|
||||
StatusCode = metadataAttribute.StatusCode,
|
||||
Type = type,
|
||||
};
|
||||
}
|
||||
else if (metadataAttribute.Type != null)
|
||||
{
|
||||
objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type;
|
||||
apiResponseType = new ApiResponseType
|
||||
{
|
||||
StatusCode = metadataAttribute.StatusCode,
|
||||
Type = metadataAttribute.Type,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
results[apiResponseType.StatusCode] = apiResponseType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set the default status only when no status has already been set explicitly
|
||||
if (objectTypes.Count == 0 && type != null)
|
||||
if (results.Count == 0 && type != null)
|
||||
{
|
||||
objectTypes[StatusCodes.Status200OK] = type;
|
||||
results[StatusCodes.Status200OK] = new ApiResponseType
|
||||
{
|
||||
StatusCode = StatusCodes.Status200OK,
|
||||
Type = type,
|
||||
};
|
||||
}
|
||||
|
||||
if (contentTypes.Count == 0)
|
||||
|
|
@ -117,25 +142,15 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
|
||||
var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType<IApiResponseTypeMetadataProvider>();
|
||||
|
||||
foreach (var objectType in objectTypes)
|
||||
foreach (var apiResponse in results.Values)
|
||||
{
|
||||
if (objectType.Value == null || objectType.Value == typeof(void))
|
||||
var responseType = apiResponse.Type;
|
||||
if (responseType == null || responseType == typeof(void))
|
||||
{
|
||||
results.Add(new ApiResponseType()
|
||||
{
|
||||
StatusCode = objectType.Key,
|
||||
Type = objectType.Value
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var apiResponseType = new ApiResponseType()
|
||||
{
|
||||
Type = objectType.Value,
|
||||
StatusCode = objectType.Key,
|
||||
ModelMetadata = _modelMetadataProvider.GetMetadataForType(objectType.Value)
|
||||
};
|
||||
apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(responseType);
|
||||
|
||||
foreach (var contentType in contentTypes)
|
||||
{
|
||||
|
|
@ -143,7 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
{
|
||||
var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(
|
||||
contentType,
|
||||
objectType.Value);
|
||||
responseType);
|
||||
|
||||
if (formatterSupportedContentTypes == null)
|
||||
{
|
||||
|
|
@ -152,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
|
||||
foreach (var formatterSupportedContentType in formatterSupportedContentTypes)
|
||||
{
|
||||
apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat()
|
||||
apiResponse.ApiResponseFormats.Add(new ApiResponseFormat
|
||||
{
|
||||
Formatter = (IOutputFormatter)responseTypeMetadataProvider,
|
||||
MediaType = formatterSupportedContentType,
|
||||
|
|
@ -160,11 +175,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(apiResponseType);
|
||||
}
|
||||
|
||||
return results;
|
||||
return results.Values;
|
||||
}
|
||||
|
||||
private Type GetDeclaredReturnType(ControllerActionDescriptor action)
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
var errorMessage = Resources.FormatApiConvention_UnsupportedAttributesOnConvention(
|
||||
methodDisplayName,
|
||||
Environment.NewLine + string.Join(Environment.NewLine, unsupportedAttributes) + Environment.NewLine,
|
||||
$"{nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}");
|
||||
$"{nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}");
|
||||
|
||||
throw new ArgumentException(errorMessage, nameof(conventionType));
|
||||
}
|
||||
|
|
@ -83,6 +83,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
private static bool IsAllowedAttribute(object attribute)
|
||||
{
|
||||
return attribute is ProducesResponseTypeAttribute ||
|
||||
attribute is ProducesDefaultResponseTypeAttribute ||
|
||||
attribute is ApiConventionNameMatchAttribute;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a return type for all HTTP status codes that are not covered by other <see cref="IApiResponseMetadataProvider"/> instances.
|
||||
/// </summary>
|
||||
public interface IApiDefaultResponseMetadataProvider : IApiResponseMetadataProvider
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -8,25 +8,53 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
{
|
||||
public static class DefaultApiConventions
|
||||
{
|
||||
#region GET
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||
public static void Get(
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
||||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object id) { }
|
||||
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||
public static void Find(
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
||||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object id)
|
||||
{ }
|
||||
#endregion
|
||||
|
||||
#region POST
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||
public static void Post(
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)]
|
||||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object model) { }
|
||||
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||
public static void Create(
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)]
|
||||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object model)
|
||||
{ }
|
||||
#endregion
|
||||
|
||||
#region PUT
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||
public static void Put(
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
||||
|
|
@ -37,13 +65,47 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object model) { }
|
||||
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||
public static void Edit(
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
||||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object id,
|
||||
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)]
|
||||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object model)
|
||||
{ }
|
||||
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||
public static void Update(
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
||||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object id,
|
||||
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)]
|
||||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object model)
|
||||
{ }
|
||||
#endregion
|
||||
|
||||
#region DELETE
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||
public static void Delete(
|
||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
||||
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
|
||||
object id) { }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
values[i] = value as string ?? Convert.ToString(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (cache.OrdinalEntries.TryGetValue(values, out var matchingRouteValues) ||
|
||||
cache.OrdinalIgnoreCaseEntries.TryGetValue(values, out matchingRouteValues))
|
||||
{
|
||||
|
|
@ -441,7 +441,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var hash = new HashCodeCombiner();
|
||||
for (var i = 0; i < obj.Length; i++)
|
||||
{
|
||||
hash.Add(obj[i], _valueComparer);
|
||||
var o = obj[i];
|
||||
|
||||
// Route values define null and "" to be equivalent.
|
||||
if (string.IsNullOrEmpty(o))
|
||||
{
|
||||
o = null;
|
||||
}
|
||||
hash.Add(o, _valueComparer);
|
||||
}
|
||||
|
||||
return hash.CombinedHash;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that specifies the <see cref="System.Type"/> for all HTTP status codes that are not covered by <see cref="ProducesResponseTypeAttribute"/>.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class ProducesDefaultResponseTypeAttribute : Attribute, IApiDefaultResponseMetadataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="ProducesResponseTypeAttribute"/>.
|
||||
/// </summary>
|
||||
public ProducesDefaultResponseTypeAttribute()
|
||||
: this(typeof(void))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="ProducesResponseTypeAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The <see cref="Type"/> of object that is going to be written in the response.</param>
|
||||
public ProducesDefaultResponseTypeAttribute(Type type)
|
||||
{
|
||||
Type = type ?? throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the value returned by an action.
|
||||
/// </summary>
|
||||
public Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP status code of the response.
|
||||
/// </summary>
|
||||
public int StatusCode { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes)
|
||||
{
|
||||
// Users are supposed to use the 'Produces' attribute to set the content types that an action can support.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1214,6 +1214,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
public virtual UnauthorizedResult Unauthorized()
|
||||
=> new UnauthorizedResult();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="PartialViewResult"/> by specifying the name of a partial to render.
|
||||
/// </summary>
|
||||
/// <param name="viewName">The partial name.</param>
|
||||
/// <returns>The created <see cref="PartialViewResult"/> object for the response.</returns>
|
||||
public virtual PartialViewResult Partial(string viewName)
|
||||
{
|
||||
return Partial(viewName, model: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="PartialViewResult"/> by specifying the name of a partial to render and the model object.
|
||||
/// </summary>
|
||||
/// <param name="viewName">The partial name.</param>
|
||||
/// <param name="model">The model to be passed into the partial.</param>
|
||||
/// <returns>The created <see cref="PartialViewResult"/> object for the response.</returns>
|
||||
public virtual PartialViewResult Partial(string viewName, object model)
|
||||
{
|
||||
ViewContext.ViewData.Model = model;
|
||||
|
||||
return new PartialViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewData = ViewContext.ViewData
|
||||
};
|
||||
}
|
||||
|
||||
#region ViewComponentResult
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ViewComponentResult"/> by specifying the name of a view component to render.
|
||||
|
|
|
|||
|
|
@ -1614,6 +1614,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
public virtual UnauthorizedResult Unauthorized()
|
||||
=> new UnauthorizedResult();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="PartialViewResult"/> by specifying the name of a partial to render.
|
||||
/// </summary>
|
||||
/// <param name="viewName">The partial name.</param>
|
||||
/// <returns>The created <see cref="PartialViewResult"/> object for the response.</returns>
|
||||
public virtual PartialViewResult Partial(string viewName)
|
||||
{
|
||||
return Partial(viewName, model: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="PartialViewResult"/> by specifying the name of a partial to render and the model object.
|
||||
/// </summary>
|
||||
/// <param name="viewName">The partial name.</param>
|
||||
/// <param name="model">The model to be passed into the partial.</param>
|
||||
/// <returns>The created <see cref="PartialViewResult"/> object for the response.</returns>
|
||||
public virtual PartialViewResult Partial(string viewName, object model)
|
||||
{
|
||||
ViewData.Model = model;
|
||||
|
||||
return new PartialViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewData = ViewData
|
||||
};
|
||||
}
|
||||
|
||||
#region ViewComponentResult
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ViewComponentResult"/> by specifying the name of a view component to render.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
{
|
||||
private const string ForAttributeName = "for";
|
||||
private const string ModelAttributeName = "model";
|
||||
private const string FallbackAttributeName = "fallback-name";
|
||||
private const string OptionalAttributeName = "optional";
|
||||
private object _model;
|
||||
private bool _hasModel;
|
||||
|
|
@ -82,6 +83,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
[HtmlAttributeName(OptionalAttributeName)]
|
||||
public bool Optional { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// View to lookup if the view specified by <see cref="Name"/> cannot be located.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(FallbackAttributeName)]
|
||||
public string FallbackName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ViewDataDictionary"/> to pass into the partial view.
|
||||
/// </summary>
|
||||
|
|
@ -104,21 +111,46 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var viewEngineResult = FindView();
|
||||
|
||||
if (viewEngineResult.Success)
|
||||
{
|
||||
var model = ResolveModel();
|
||||
var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize);
|
||||
using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8))
|
||||
{
|
||||
await RenderPartialViewAsync(writer, model, viewEngineResult.View);
|
||||
output.Content.SetHtmlContent(viewBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the TagName. We don't want `partial` to render.
|
||||
output.TagName = null;
|
||||
|
||||
var result = FindView(Name);
|
||||
var viewSearchedLocations = result.SearchedLocations;
|
||||
var fallBackViewSearchedLocations = Enumerable.Empty<string>();
|
||||
|
||||
if (!result.Success && !string.IsNullOrEmpty(FallbackName))
|
||||
{
|
||||
result = FindView(FallbackName);
|
||||
fallBackViewSearchedLocations = result.SearchedLocations;
|
||||
}
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
if (Optional)
|
||||
{
|
||||
// Could not find the view or fallback view, but the partial is marked as optional.
|
||||
return;
|
||||
}
|
||||
|
||||
var locations = Environment.NewLine + string.Join(Environment.NewLine, viewSearchedLocations);
|
||||
var errorMessage = Resources.FormatViewEngine_PartialViewNotFound(Name, locations);
|
||||
|
||||
if (!string.IsNullOrEmpty(FallbackName))
|
||||
{
|
||||
locations = Environment.NewLine + string.Join(Environment.NewLine, result.SearchedLocations);
|
||||
errorMessage += Environment.NewLine + Resources.FormatViewEngine_FallbackViewNotFound(FallbackName, locations);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(errorMessage);
|
||||
}
|
||||
|
||||
var model = ResolveModel();
|
||||
var viewBuffer = new ViewBuffer(_viewBufferScope, result.ViewName, ViewBuffer.PartialViewPageSize);
|
||||
using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8))
|
||||
{
|
||||
await RenderPartialViewAsync(writer, model, result.View);
|
||||
output.Content.SetHtmlContent(viewBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
|
|
@ -152,26 +184,19 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
return ViewContext.ViewData.Model;
|
||||
}
|
||||
|
||||
private ViewEngineResult FindView()
|
||||
private ViewEngineResult FindView(string partialName)
|
||||
{
|
||||
var viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, Name, isMainPage: false);
|
||||
var viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, partialName, isMainPage: false);
|
||||
var getViewLocations = viewEngineResult.SearchedLocations;
|
||||
if (!viewEngineResult.Success)
|
||||
{
|
||||
viewEngineResult = _viewEngine.FindView(ViewContext, Name, isMainPage: false);
|
||||
viewEngineResult = _viewEngine.FindView(ViewContext, partialName, isMainPage: false);
|
||||
}
|
||||
|
||||
if (!viewEngineResult.Success && !Optional)
|
||||
if (!viewEngineResult.Success)
|
||||
{
|
||||
var searchedLocations = Enumerable.Concat(getViewLocations, viewEngineResult.SearchedLocations);
|
||||
var locations = string.Empty;
|
||||
if (searchedLocations.Any())
|
||||
{
|
||||
locations += Environment.NewLine + string.Join(Environment.NewLine, searchedLocations);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatViewEngine_PartialViewNotFound(Name, locations));
|
||||
return ViewEngineResult.NotFound(partialName, searchedLocations);
|
||||
}
|
||||
|
||||
return viewEngineResult;
|
||||
|
|
|
|||
|
|
@ -206,6 +206,20 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
internal static string FormatPartialTagHelper_InvalidModelAttributes(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("PartialTagHelper_InvalidModelAttributes"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// The fallback partial view '{0}' was not found. The following locations were searched:{1}
|
||||
/// </summary>
|
||||
internal static string ViewEngine_FallbackViewNotFound
|
||||
{
|
||||
get => GetString("ViewEngine_FallbackViewNotFound");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The fallback partial view '{0}' was not found. The following locations were searched:{1}
|
||||
/// </summary>
|
||||
internal static string FormatViewEngine_FallbackViewNotFound(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ViewEngine_FallbackViewNotFound"), p0, p1);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -159,4 +159,7 @@
|
|||
<data name="PartialTagHelper_InvalidModelAttributes" xml:space="preserve">
|
||||
<value>Cannot use '{0}' with both '{1}' and '{2}' attributes.</value>
|
||||
</data>
|
||||
<data name="ViewEngine_FallbackViewNotFound" xml:space="preserve">
|
||||
<value>The fallback partial view '{0}' was not found. The following locations were searched:{1}</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -72,6 +72,60 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
public Task<ActionResult<BaseModel>> Get(int id) => null;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_CombinesFilters()
|
||||
{
|
||||
// Arrange
|
||||
var filterDescriptors = new[]
|
||||
{
|
||||
new FilterDescriptor(new ProducesResponseTypeAttribute(400), FilterScope.Global),
|
||||
new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(object), 201), FilterScope.Controller),
|
||||
new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(ProblemDetails), 400), FilterScope.Controller),
|
||||
new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(BaseModel), 201), FilterScope.Action),
|
||||
new FilterDescriptor(new ProducesResponseTypeAttribute(404), FilterScope.Action),
|
||||
};
|
||||
|
||||
var actionDescriptor = new ControllerActionDescriptor
|
||||
{
|
||||
FilterDescriptors = filterDescriptors,
|
||||
MethodInfo = typeof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController).GetMethod(nameof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController.Get)),
|
||||
};
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(201, responseType.StatusCode);
|
||||
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Equal(typeof(ProblemDetails), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(404, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_ReturnsResponseTypesFromApiConventionItem()
|
||||
{
|
||||
|
|
@ -159,6 +213,54 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
public Task<ActionResult<BaseModel>> PostModel(int id, BaseModel model) => null;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_ReturnsDefaultProblemResponse()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase));
|
||||
actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[]
|
||||
{
|
||||
new ProducesResponseTypeAttribute(201),
|
||||
new ProducesResponseTypeAttribute(404),
|
||||
new ProducesDefaultResponseTypeAttribute(typeof(SerializableError)),
|
||||
});
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.True(responseType.IsDefaultResponse);
|
||||
Assert.Equal(typeof(SerializableError), responseType.Type);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(201, responseType.StatusCode);
|
||||
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(404, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
});
|
||||
}
|
||||
|
||||
private static ApiResponseTypeProvider GetProvider()
|
||||
{
|
||||
var mvcOptions = new MvcOptions
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
|
|
@ -15,11 +16,10 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public void Constructor_ThrowsIfConventionMethodIsAnnotatedWithProducesAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"Method {typeof(ConventionWithProducesAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" +
|
||||
Environment.NewLine +
|
||||
typeof(ProducesAttribute).FullName +
|
||||
Environment.NewLine +
|
||||
$"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}";
|
||||
var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get);
|
||||
var attribute = typeof(ProducesAttribute);
|
||||
|
||||
var expected = GetErrorMessage(methodName, attribute);
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
|
|
@ -38,11 +38,9 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public void Constructor_ThrowsIfConventionMethodHasRouteAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"Method {typeof(ConventionWithRouteAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" +
|
||||
Environment.NewLine +
|
||||
typeof(HttpGetAttribute).FullName +
|
||||
Environment.NewLine +
|
||||
$"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}";
|
||||
var methodName = typeof(ConventionWithRouteAttribute).FullName + '.' + nameof(ConventionWithRouteAttribute.Get);
|
||||
var attribute = typeof(HttpGetAttribute);
|
||||
var expected = GetErrorMessage(methodName, attribute);
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
|
|
@ -61,11 +59,9 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public void Constructor_ThrowsIfMultipleUnsupportedAttributesArePresentOnConvention()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"Method {typeof(ConventionWitUnsupportedAttributes).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" +
|
||||
Environment.NewLine +
|
||||
string.Join(Environment.NewLine, typeof(ProducesAttribute).FullName, typeof(ServiceFilterAttribute).FullName, typeof(AuthorizeAttribute).FullName) +
|
||||
Environment.NewLine +
|
||||
$"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}";
|
||||
var methodName = typeof(ConventionWitUnsupportedAttributes).FullName + '.' + nameof(ConventionWitUnsupportedAttributes.Get);
|
||||
var attributes = new[] { typeof(ProducesAttribute), typeof(ServiceFilterAttribute), typeof(AuthorizeAttribute) };
|
||||
var expected = GetErrorMessage(methodName, attributes);
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
|
|
@ -82,5 +78,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
[Authorize]
|
||||
public static void Get() { }
|
||||
}
|
||||
|
||||
private static string GetErrorMessage(string methodName, params Type[] attributes)
|
||||
{
|
||||
return $"Method {methodName} is decorated with the following attributes that are not allowed on an API convention method:" +
|
||||
Environment.NewLine +
|
||||
string.Join(Environment.NewLine, attributes.Select(a => a.FullName)) +
|
||||
Environment.NewLine +
|
||||
$"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
Assert.True(result);
|
||||
Assert.Collection(
|
||||
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
||||
r => Assert.IsAssignableFrom<IApiDefaultResponseMetadataProvider>(r),
|
||||
r => Assert.Equal(200, r.StatusCode),
|
||||
r => Assert.Equal(404, r.StatusCode));
|
||||
}
|
||||
|
|
@ -130,6 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
Assert.True(result);
|
||||
Assert.Collection(
|
||||
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
||||
r => Assert.IsAssignableFrom<IApiDefaultResponseMetadataProvider>(r),
|
||||
r => Assert.Equal(201, r.StatusCode),
|
||||
r => Assert.Equal(400, r.StatusCode));
|
||||
}
|
||||
|
|
@ -152,6 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
Assert.True(result);
|
||||
Assert.Collection(
|
||||
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
||||
r => Assert.IsAssignableFrom<IApiDefaultResponseMetadataProvider>(r),
|
||||
r => Assert.Equal(204, r.StatusCode),
|
||||
r => Assert.Equal(400, r.StatusCode),
|
||||
r => Assert.Equal(404, r.StatusCode));
|
||||
|
|
@ -175,6 +178,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
Assert.True(result);
|
||||
Assert.Collection(
|
||||
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
||||
r => Assert.IsAssignableFrom<IApiDefaultResponseMetadataProvider>(r),
|
||||
r => Assert.Equal(200, r.StatusCode),
|
||||
r => Assert.Equal(400, r.StatusCode),
|
||||
r => Assert.Equal(404, r.StatusCode));
|
||||
|
|
|
|||
|
|
@ -277,6 +277,176 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
Assert.Equal(expected, candidates);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectCandidates_Match_CaseSensitiveMatch_MatchesOnEmptyString()
|
||||
{
|
||||
var actions = new ActionDescriptor[]
|
||||
{
|
||||
new ActionDescriptor()
|
||||
{
|
||||
DisplayName = "A1",
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "area", null },
|
||||
{ "controller", "Home" },
|
||||
{ "action", "Index" }
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
|
||||
var routeContext = CreateRouteContext("GET");
|
||||
// Example: In conventional route, one could set non-inline defaults
|
||||
// new { area = "", controller = "Home", action = "Index" }
|
||||
routeContext.RouteData.Values.Add("area", "");
|
||||
routeContext.RouteData.Values.Add("controller", "Home");
|
||||
routeContext.RouteData.Values.Add("action", "Index");
|
||||
|
||||
// Act
|
||||
var candidates = selector.SelectCandidates(routeContext);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(candidates);
|
||||
Assert.Same(actions[0], action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectCandidates_Match_CaseInsensitiveMatch_MatchesOnEmptyString()
|
||||
{
|
||||
var actions = new ActionDescriptor[]
|
||||
{
|
||||
new ActionDescriptor()
|
||||
{
|
||||
DisplayName = "A1",
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "area", null },
|
||||
{ "controller", "Home" },
|
||||
{ "action", "Index" }
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
|
||||
var routeContext = CreateRouteContext("GET");
|
||||
// Example: In conventional route, one could set non-inline defaults
|
||||
// new { area = "", controller = "Home", action = "Index" }
|
||||
routeContext.RouteData.Values.Add("area", "");
|
||||
routeContext.RouteData.Values.Add("controller", "HoMe");
|
||||
routeContext.RouteData.Values.Add("action", "InDeX");
|
||||
|
||||
// Act
|
||||
var candidates = selector.SelectCandidates(routeContext);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(candidates);
|
||||
Assert.Same(actions[0], action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectCandidates_Match_MatchesOnNull()
|
||||
{
|
||||
var actions = new ActionDescriptor[]
|
||||
{
|
||||
new ActionDescriptor()
|
||||
{
|
||||
DisplayName = "A1",
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "area", null },
|
||||
{ "controller", "Home" },
|
||||
{ "action", "Index" }
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
|
||||
var routeContext = CreateRouteContext("GET");
|
||||
// Example: In conventional route, one could set non-inline defaults
|
||||
// new { area = (string)null, controller = "Foo", action = "Index" }
|
||||
routeContext.RouteData.Values.Add("area", null);
|
||||
routeContext.RouteData.Values.Add("controller", "Home");
|
||||
routeContext.RouteData.Values.Add("action", "Index");
|
||||
|
||||
// Act
|
||||
var candidates = selector.SelectCandidates(routeContext);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(candidates);
|
||||
Assert.Same(actions[0], action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectCandidates_Match_ActionDescriptorWithEmptyRouteValues_MatchesOnEmptyString()
|
||||
{
|
||||
var actions = new ActionDescriptor[]
|
||||
{
|
||||
new ActionDescriptor()
|
||||
{
|
||||
DisplayName = "A1",
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "foo", "" },
|
||||
{ "controller", "Home" },
|
||||
{ "action", "Index" }
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
|
||||
var routeContext = CreateRouteContext("GET");
|
||||
// Example: In conventional route, one could set non-inline defaults
|
||||
// new { area = (string)null, controller = "Home", action = "Index" }
|
||||
routeContext.RouteData.Values.Add("foo", "");
|
||||
routeContext.RouteData.Values.Add("controller", "Home");
|
||||
routeContext.RouteData.Values.Add("action", "Index");
|
||||
|
||||
// Act
|
||||
var candidates = selector.SelectCandidates(routeContext);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(candidates);
|
||||
Assert.Same(actions[0], action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectCandidates_Match_ActionDescriptorWithEmptyRouteValues_MatchesOnNull()
|
||||
{
|
||||
var actions = new ActionDescriptor[]
|
||||
{
|
||||
new ActionDescriptor()
|
||||
{
|
||||
DisplayName = "A1",
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "foo", "" },
|
||||
{ "controller", "Home" },
|
||||
{ "action", "Index" }
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
|
||||
var routeContext = CreateRouteContext("GET");
|
||||
// Example: In conventional route, one could set non-inline defaults
|
||||
// new { area = (string)null, controller = "Home", action = "Index" }
|
||||
routeContext.RouteData.Values.Add("foo", null);
|
||||
routeContext.RouteData.Values.Add("controller", "Home");
|
||||
routeContext.RouteData.Values.Add("action", "Index");
|
||||
|
||||
// Act
|
||||
var candidates = selector.SelectCandidates(routeContext);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(candidates);
|
||||
Assert.Same(actions[0], action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectBestCandidate_AmbiguousActions_LogIsCorrect()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
// 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.Linq;
|
||||
using System.Net.Http;
|
||||
using AngleSharp.Dom.Html;
|
||||
using AngleSharp.Parser.Html;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
|
|
@ -19,27 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var parser = new HtmlParser();
|
||||
var htmlDocument = parser.Parse(htmlContent);
|
||||
|
||||
return RetrieveAntiforgeryToken(htmlDocument);
|
||||
}
|
||||
|
||||
public static string RetrieveAntiforgeryToken(IHtmlDocument htmlDocument)
|
||||
{
|
||||
var hiddenInputs = htmlDocument.QuerySelectorAll("form input[type=hidden]");
|
||||
foreach (var input in hiddenInputs)
|
||||
{
|
||||
if (!input.HasAttribute("name"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = input.GetAttribute("name");
|
||||
if (name == "__RequestVerificationToken" || name == "HtmlEncode[[__RequestVerificationToken]]")
|
||||
{
|
||||
return input.GetAttribute("value");
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Antiforgery token could not be located in {htmlDocument.Source.Text}.");
|
||||
return htmlDocument.RetrieveAntiforgeryToken();
|
||||
}
|
||||
|
||||
public static CookieMetadata RetrieveAntiforgeryCookie(HttpResponseMessage response)
|
||||
|
|
|
|||
|
|
@ -711,7 +711,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
|
||||
// Assert
|
||||
var description = Assert.Single(result);
|
||||
Assert.Equal(2, description.SupportedResponseTypes.Count);
|
||||
|
||||
Assert.Collection(
|
||||
description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
|
||||
|
|
@ -749,7 +748,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
|
||||
// Assert
|
||||
var description = Assert.Single(result);
|
||||
Assert.Equal(2, description.SupportedResponseTypes.Count);
|
||||
|
||||
Assert.Collection(
|
||||
description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
|
||||
|
|
@ -1171,6 +1169,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Collection(
|
||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.True(responseType.IsDefaultResponse);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(Product).FullName, responseType.ResponseType);
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
|
|
@ -1255,6 +1257,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Collection(
|
||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.True(responseType.IsDefaultResponse);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(201, responseType.StatusCode);
|
||||
|
|
@ -1283,6 +1289,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Collection(
|
||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.True(responseType.IsDefaultResponse);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(204, responseType.StatusCode);
|
||||
|
|
@ -1316,6 +1326,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Collection(
|
||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.True(responseType.IsDefaultResponse);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
|
|
|
|||
|
|
@ -566,7 +566,7 @@ Products: Music Systems, Televisions (3)";
|
|||
var document = await Client.GetHtmlDocumentAsync(url);
|
||||
|
||||
// Assert
|
||||
var banner = QuerySelector(document, ".banner");
|
||||
var banner = document.RequiredQuerySelector(".banner");
|
||||
Assert.Equal("Some status message", banner.TextContent);
|
||||
}
|
||||
|
||||
|
|
@ -581,19 +581,36 @@ Products: Music Systems, Televisions (3)";
|
|||
var document = await Client.GetHtmlDocumentAsync(url);
|
||||
|
||||
// Assert
|
||||
var banner = QuerySelector(document, ".banner");
|
||||
var banner = document.RequiredQuerySelector(".banner");
|
||||
Assert.Empty(banner.TextContent);
|
||||
}
|
||||
|
||||
private static IElement QuerySelector(IHtmlDocument document, string selector)
|
||||
[Fact]
|
||||
public async Task PartialTagHelper_AllowsUsingFallback()
|
||||
{
|
||||
var element = document.QuerySelector(selector);
|
||||
if (element == null)
|
||||
{
|
||||
throw new ArgumentException($"Document does not contain element that matches the selector {selector}: " + Environment.NewLine + document.DocumentElement.OuterHtml);
|
||||
}
|
||||
// Arrange
|
||||
var url = "/Customer/PartialWithFallback";
|
||||
|
||||
return element;
|
||||
// Act
|
||||
var document = await Client.GetHtmlDocumentAsync(url);
|
||||
|
||||
// Assert
|
||||
var content = document.RequiredQuerySelector("#content");
|
||||
Assert.Equal("Hello from fallback", content.TextContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PartialTagHelper_AllowsUsingOptional()
|
||||
{
|
||||
// Arrange
|
||||
var url = "/Customer/PartialWithOptional";
|
||||
|
||||
// Act
|
||||
var document = await Client.GetHtmlDocumentAsync(url);
|
||||
|
||||
// Assert
|
||||
var content = document.RequiredQuerySelector("#content");
|
||||
Assert.Empty(content.TextContent);
|
||||
}
|
||||
|
||||
private static HttpRequestMessage RequestWithLocale(string url, string locale)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// 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 AngleSharp.Dom;
|
||||
using AngleSharp.Dom.Html;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public static class IHtmlDocumentExtensions
|
||||
{
|
||||
public static IElement RequiredQuerySelector(this IHtmlDocument document, string selector)
|
||||
{
|
||||
var element = document.QuerySelector(selector);
|
||||
if (element == null)
|
||||
{
|
||||
throw new ArgumentException($"Document does not contain element that matches the selector {selector}: " + Environment.NewLine + document.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public static string RetrieveAntiforgeryToken(this IHtmlDocument htmlDocument)
|
||||
{
|
||||
var hiddenInputs = htmlDocument.QuerySelectorAll("form input[type=hidden]");
|
||||
foreach (var input in hiddenInputs)
|
||||
{
|
||||
if (!input.HasAttribute("name"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = input.GetAttribute("name");
|
||||
if (name == "__RequestVerificationToken" || name == "HtmlEncode[[__RequestVerificationToken]]")
|
||||
{
|
||||
return input.GetAttribute("value");
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Antiforgery token could not be located in {htmlDocument.Source.Text}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ using System.Net.Http.Headers;
|
|||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
|
@ -144,6 +143,26 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("CustomActionResult", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Page_Handler_ReturnPartialWithoutModel()
|
||||
{
|
||||
// Act
|
||||
var document = await Client.GetHtmlDocumentAsync("RenderPartialWithoutModel");
|
||||
|
||||
var element = document.RequiredQuerySelector("#content");
|
||||
Assert.Equal("Welcome, Guest", element.TextContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Page_Handler_ReturnPartialWithModel()
|
||||
{
|
||||
// Act
|
||||
var document = await Client.GetHtmlDocumentAsync("RenderPartialWithModel");
|
||||
|
||||
var element = document.RequiredQuerySelector("#content");
|
||||
Assert.Equal("Welcome, Admin", element.TextContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Page_Handler_AsyncReturnTypeImplementsIActionResult()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1894,6 +1894,52 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
testPageModel.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PartialView_WithName()
|
||||
{
|
||||
// Arrange
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
|
||||
var pageModel = new TestPageModel
|
||||
{
|
||||
PageContext = new PageContext
|
||||
{
|
||||
ViewData = viewData
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = pageModel.Partial("LoginStatus");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("LoginStatus", result.ViewName);
|
||||
Assert.Same(viewData, result.ViewData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PartialView_WithNameAndModel()
|
||||
{
|
||||
// Arrange
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
|
||||
var pageModel = new TestPageModel
|
||||
{
|
||||
PageContext = new PageContext
|
||||
{
|
||||
ViewData = viewData
|
||||
}
|
||||
};
|
||||
var model = new { Username = "Admin" };
|
||||
|
||||
// Act
|
||||
var result = pageModel.Partial("LoginStatus", model);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("LoginStatus", result.ViewName);
|
||||
Assert.Equal(model, result.Model);
|
||||
Assert.Same(viewData, result.ViewData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewComponent_WithName()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1697,6 +1697,52 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
Assert.Equal(statusCode, result.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PartialView_WithName()
|
||||
{
|
||||
// Arrange
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
|
||||
var pageModel = new TestPage
|
||||
{
|
||||
ViewContext = new ViewContext
|
||||
{
|
||||
ViewData = viewData
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = pageModel.Partial("LoginStatus");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("LoginStatus", result.ViewName);
|
||||
Assert.Same(viewData, result.ViewData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PartialView_WithNameAndModel()
|
||||
{
|
||||
// Arrange
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
|
||||
var pageModel = new TestPage
|
||||
{
|
||||
ViewContext = new ViewContext
|
||||
{
|
||||
ViewData = viewData
|
||||
}
|
||||
};
|
||||
var model = new { Username = "Admin" };
|
||||
|
||||
// Act
|
||||
var result = pageModel.Partial("LoginStatus", model);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("LoginStatus", result.ViewName);
|
||||
Assert.Equal(model, result.Model);
|
||||
Assert.Same(viewData, result.ViewData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewComponent_WithName()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -648,6 +648,198 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
Assert.Empty(content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_RendersMainPartial_If_FallbackIsSet_AndMainPartialIsFound()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Hello from partial!";
|
||||
var bufferScope = new TestViewBufferScope();
|
||||
var partialName = "_Partial";
|
||||
var fallbackName = "_Fallback";
|
||||
var model = new object();
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
var view = new Mock<IView>();
|
||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Callback((ViewContext v) =>
|
||||
{
|
||||
v.Writer.Write(expected);
|
||||
})
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var fallbackView = new Mock<IView>();
|
||||
fallbackView.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Callback((ViewContext v) =>
|
||||
{
|
||||
v.Writer.Write("Hello from fallback partial!");
|
||||
})
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), partialName, false))
|
||||
.Returns(ViewEngineResult.Found(partialName, view.Object));
|
||||
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), fallbackName, false))
|
||||
.Returns(ViewEngineResult.Found(fallbackName, fallbackView.Object));
|
||||
|
||||
var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope)
|
||||
{
|
||||
Name = partialName,
|
||||
ViewContext = viewContext,
|
||||
FallbackName = fallbackName
|
||||
};
|
||||
var tagHelperContext = GetTagHelperContext();
|
||||
var output = GetTagHelperOutput();
|
||||
|
||||
// Act
|
||||
await tagHelper.ProcessAsync(tagHelperContext, output);
|
||||
|
||||
// Assert
|
||||
var content = HtmlContentUtilities.HtmlContentToString(output.Content, new HtmlTestEncoder());
|
||||
Assert.Equal(expected, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_IfHasFallback_Throws_When_MainPartialAndFallback_AreNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var bufferScope = new TestViewBufferScope();
|
||||
var partialName = "_Partial";
|
||||
var fallbackName = "_Fallback";
|
||||
var expected = string.Join(
|
||||
Environment.NewLine,
|
||||
$"The partial view '{partialName}' was not found. The following locations were searched:",
|
||||
"PartialNotFound1",
|
||||
"PartialNotFound2",
|
||||
"PartialNotFound3",
|
||||
"PartialNotFound4",
|
||||
$"The fallback partial view '{fallbackName}' was not found. The following locations were searched:",
|
||||
"FallbackNotFound1",
|
||||
"FallbackNotFound2",
|
||||
"FallbackNotFound3",
|
||||
"FallbackNotFound4");
|
||||
var viewData = new ViewDataDictionary(new TestModelMetadataProvider(), new ModelStateDictionary());
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
var view = Mock.Of<IView>();
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), partialName, false))
|
||||
.Returns(ViewEngineResult.NotFound(partialName, new[] { "PartialNotFound1", "PartialNotFound2" }));
|
||||
|
||||
viewEngine.Setup(v => v.FindView(viewContext, partialName, false))
|
||||
.Returns(ViewEngineResult.NotFound(partialName, new[] { $"PartialNotFound3", $"PartialNotFound4" }));
|
||||
|
||||
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), fallbackName, false))
|
||||
.Returns(ViewEngineResult.NotFound(partialName, new[] { "FallbackNotFound1", "FallbackNotFound2" }));
|
||||
|
||||
viewEngine.Setup(v => v.FindView(viewContext, fallbackName, false))
|
||||
.Returns(ViewEngineResult.NotFound(partialName, new[] { $"FallbackNotFound3", $"FallbackNotFound4" }));
|
||||
|
||||
var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope)
|
||||
{
|
||||
Name = partialName,
|
||||
ViewContext = viewContext,
|
||||
ViewData = viewData,
|
||||
FallbackName = fallbackName
|
||||
};
|
||||
var tagHelperContext = GetTagHelperContext();
|
||||
var output = GetTagHelperOutput();
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => tagHelper.ProcessAsync(tagHelperContext, output));
|
||||
Assert.Equal(expected, exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_RendersFallbackView_If_MainIsNotFound_AndGetViewReturnsView()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Hello from fallback!";
|
||||
var bufferScope = new TestViewBufferScope();
|
||||
var partialName = "_Partial";
|
||||
var fallbackName = "_Fallback";
|
||||
var model = new object();
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
var view = new Mock<IView>();
|
||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Callback((ViewContext v) =>
|
||||
{
|
||||
v.Writer.Write(expected);
|
||||
})
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), partialName, false))
|
||||
.Returns(ViewEngineResult.NotFound(partialName, Array.Empty<string>()));
|
||||
viewEngine.Setup(v => v.FindView(viewContext, partialName, false))
|
||||
.Returns(ViewEngineResult.NotFound(partialName, Array.Empty<string>()));
|
||||
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), fallbackName, false))
|
||||
.Returns(ViewEngineResult.Found(fallbackName, view.Object));
|
||||
|
||||
var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope)
|
||||
{
|
||||
Name = partialName,
|
||||
ViewContext = viewContext,
|
||||
FallbackName = fallbackName
|
||||
};
|
||||
var tagHelperContext = GetTagHelperContext();
|
||||
var output = GetTagHelperOutput();
|
||||
|
||||
// Act
|
||||
await tagHelper.ProcessAsync(tagHelperContext, output);
|
||||
|
||||
// Assert
|
||||
var content = HtmlContentUtilities.HtmlContentToString(output.Content, new HtmlTestEncoder());
|
||||
Assert.Equal(expected, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_RendersFallbackView_If_MainIsNotFound_AndFindViewReturnsView()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Hello from fallback!";
|
||||
var bufferScope = new TestViewBufferScope();
|
||||
var partialName = "_Partial";
|
||||
var fallbackName = "_Fallback";
|
||||
var model = new object();
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
var view = new Mock<IView>();
|
||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Callback((ViewContext v) =>
|
||||
{
|
||||
v.Writer.Write(expected);
|
||||
})
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), partialName, false))
|
||||
.Returns(ViewEngineResult.NotFound(partialName, Array.Empty<string>()));
|
||||
viewEngine.Setup(v => v.FindView(viewContext, partialName, false))
|
||||
.Returns(ViewEngineResult.NotFound(partialName, Array.Empty<string>()));
|
||||
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), fallbackName, false))
|
||||
.Returns(ViewEngineResult.NotFound(fallbackName, Array.Empty<string>()));
|
||||
viewEngine.Setup(v => v.FindView(viewContext, fallbackName, false))
|
||||
.Returns(ViewEngineResult.Found(fallbackName, view.Object));
|
||||
|
||||
var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope)
|
||||
{
|
||||
Name = partialName,
|
||||
ViewContext = viewContext,
|
||||
FallbackName = fallbackName
|
||||
};
|
||||
var tagHelperContext = GetTagHelperContext();
|
||||
var output = GetTagHelperOutput();
|
||||
|
||||
// Act
|
||||
await tagHelper.ProcessAsync(tagHelperContext, output);
|
||||
|
||||
// Assert
|
||||
var content = HtmlContentUtilities.HtmlContentToString(output.Content, new HtmlTestEncoder());
|
||||
Assert.Equal(expected, content);
|
||||
}
|
||||
|
||||
private static ViewContext GetViewContext()
|
||||
{
|
||||
return new ViewContext(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
@page
|
||||
|
||||
<partial name="_DoesNotExist" fallback-name="./_Fallback" />
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@page
|
||||
|
||||
<div id="content"><partial name="_DoesNotExist" optional="true" /></div>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<span id="content">Hello from fallback</span>
|
||||
|
|
@ -0,0 +1 @@
|
|||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace RazorPagesWebSite
|
||||
{
|
||||
public class RenderPartialWithModel : PageModel
|
||||
{
|
||||
public IActionResult OnGet() => Partial("_PartialWithModel", this);
|
||||
|
||||
public string Username => "Admin";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
@page
|
||||
@model RazorPagesWebSite.RenderPartialWithModel
|
||||
|
||||
<p>The partial will be loaded here ...</p>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
@page
|
||||
|
||||
@functions {
|
||||
public IActionResult OnGet() => Partial("_PartialWithoutModel");
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@model RazorPagesWebSite.RenderPartialWithModel
|
||||
|
||||
<span id="content">Welcome, @Model.Username</span>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<span id="content">Welcome, Guest</span>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>3.0.0</VersionPrefix>
|
||||
<VersionSuffix>alpha1</VersionSuffix>
|
||||
|
|
|
|||
Loading…
Reference in New Issue