using Microsoft.AspNetCore.Mvc.ModelBinding;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.BackOffice.Controllers;
namespace Umbraco.Cms.Web.BackOffice.ModelBinders;
///
/// The model binder for
///
internal class MemberBinder : IModelBinder
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IJsonSerializer _jsonSerializer;
private readonly IMemberService _memberService;
private readonly IMemberTypeService _memberTypeService;
private readonly ContentModelBinderHelper _modelBinderHelper;
private readonly IShortStringHelper _shortStringHelper;
private readonly IUmbracoMapper _umbracoMapper;
public MemberBinder(
IJsonSerializer jsonSerializer,
IHostingEnvironment hostingEnvironment,
IShortStringHelper shortStringHelper,
IUmbracoMapper umbracoMapper,
IMemberService memberService,
IMemberTypeService memberTypeService)
{
_jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper));
_umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
_memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
_modelBinderHelper = new ContentModelBinderHelper();
}
///
/// Creates the model from the request and binds it to the context
///
///
///
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
MemberSave? model =
await _modelBinderHelper.BindModelFromMultipartRequestAsync(_jsonSerializer, _hostingEnvironment, bindingContext);
if (model == null)
{
return;
}
model.PersistedContent =
ContentControllerBase.IsCreatingAction(model.Action) ? CreateNew(model) : GetExisting(model);
//create the dto from the persisted model
if (model.PersistedContent != null)
{
model.PropertyCollectionDto =
_umbracoMapper.Map(model.PersistedContent);
//now map all of the saved values to the dto
_modelBinderHelper.MapPropertyValuesFromSaved(model, model.PropertyCollectionDto);
}
model.Name = model.Name?.Trim();
bindingContext.Result = ModelBindingResult.Success(model);
}
///
/// Returns an IMember instance used to bind values to and save (depending on the membership scenario)
///
///
///
private IMember GetExisting(MemberSave model) => GetExisting(model.Key);
private IMember GetExisting(Guid key)
{
IMember? member = _memberService.GetByKey(key);
if (member == null)
{
throw new InvalidOperationException("Could not find member with key " + key);
}
return member;
}
///
/// Gets an instance of IMember used when creating a member
///
///
///
///
/// Depending on whether a custom membership provider is configured this will return different results.
///
private IMember CreateNew(MemberSave model)
{
IMemberType? contentType = _memberTypeService.Get(model.ContentTypeAlias);
if (contentType == null)
{
throw new InvalidOperationException("No member type found with alias " + model.ContentTypeAlias);
}
//remove all membership properties, these values are set with the membership provider.
FilterMembershipProviderProperties(contentType);
//return the new member with the details filled in
return new Member(model.Name, model.Email, model.Username, model.Password?.NewPassword, contentType);
}
///
/// This will remove all of the special membership provider properties which were required to display the property
/// editors
/// for editing - but the values have been mapped back to the MemberSave object directly - we don't want to keep these
/// properties
/// on the IMember because they will attempt to be persisted which we don't want since they might not even exist.
///
///
private void FilterMembershipProviderProperties(IContentTypeBase contentType)
{
Dictionary defaultProps =
ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper);
//remove all membership properties, these values are set with the membership provider.
var exclude = defaultProps.Select(x => x.Value.Alias).ToArray();
FilterContentTypeProperties(contentType, exclude);
}
private void FilterContentTypeProperties(IContentTypeBase contentType, IEnumerable exclude)
{
//remove all properties based on the exclusion list
foreach (var remove in exclude)
{
if (contentType.PropertyTypeExists(remove))
{
contentType.RemovePropertyType(remove);
}
}
}
}