/// <summary> /// Populates the view model from the data received from the request. /// </summary> /// <returns></returns> public void PopulateViewModel(DotvvmRequestContext context, string serializedPostData) { // get properties var data = context.ReceivedViewModelJson = JObject.Parse(serializedPostData); var viewModelToken = (JObject)data["viewModel"]; // load CSRF token context.CsrfToken = viewModelToken["$csrfToken"].Value <string>(); ViewModelJsonConverter viewModelConverter; if (viewModelToken["$encryptedValues"] != null) { // load encrypted values var encryptedValuesString = viewModelToken["$encryptedValues"].Value <string>(); viewModelConverter = new ViewModelJsonConverter(context.IsPostBack, JObject.Parse(viewModelProtector.Unprotect(encryptedValuesString, context))); } else { viewModelConverter = new ViewModelJsonConverter(context.IsPostBack); } // get validation path context.ModelState.ValidationTargetPath = data["validationTargetPath"].Value <string>(); // populate the ViewModel var serializer = CreateJsonSerializer(); serializer.Converters.Add(viewModelConverter); viewModelConverter.Populate(viewModelToken, serializer, context.ViewModel); }
/// <summary> /// Builds the view model. /// </summary> /// <param name="state">The state.</param> /// <exception cref="Exception"></exception> public virtual void BuildViewModel(ViewModelState state) { var jsonSerializer = CreateJsonSerializer(); var viewModelConverter = new ViewModelJsonConverter(true, Mapper) { UsedSerializationMaps = new HashSet <ViewModelSerializationMap>() }; jsonSerializer.Converters.Add(viewModelConverter); var writer = new JTokenWriter(); try { jsonSerializer.Serialize(writer, state.LastSentViewModel); } catch (Exception ex) { throw new Exception( $"Could not serialize viewModel of type {state.LastSentViewModel.GetType().Name}. Serialization failed at property {writer.Path}.", ex); } writer.Token["$csrfToken"] = state.CsrfToken; var result = new JObject(); result["viewModel"] = writer.Token; result["action"] = WebSocketRequestType.SuccessfulCommand; state.ChangedViewModelJson = result; }
protected override void DefaultVisit(JsNode node) { base.DefaultVisit(node); if (node.Annotation <VMPropertyInfoAnnotation>() is VMPropertyInfoAnnotation propAnnotation) { var target = node.GetChildByRole(JsTreeRoles.TargetExpression); if (target.HasAnnotation <ObservableUnwrapInvocationAnnotation>()) { target = target.GetChildByRole(JsTreeRoles.TargetExpression); } else if (target.HasAnnotation <ObservableSetterInvocationAnnotation>()) { throw new NotImplementedException(); } var propertyType = propAnnotation.MemberInfo.GetResultType(); var containsObservables = true; if (propAnnotation.SerializationMap == null && target?.Annotation <ViewModelInfoAnnotation>() is ViewModelInfoAnnotation targetAnnotation) { propAnnotation.SerializationMap = targetAnnotation.SerializationMap.Properties.FirstOrDefault(p => p.PropertyInfo == propAnnotation.MemberInfo); containsObservables = targetAnnotation.ContainsObservables; } if (propAnnotation.SerializationMap is ViewModelPropertyMap propertyMap) { if (propertyMap.ViewModelProtection == ViewModel.ProtectMode.EncryptData) { throw new Exception($"Property {propAnnotation.MemberInfo.Name} is encrypted and cannot be used in JS."); } if (node is JsMemberAccessExpression memberAccess && propertyMap.Name != memberAccess.MemberName) { memberAccess.MemberName = propertyMap.Name; } } else if (propAnnotation.MemberInfo is FieldInfo) { throw new NotSupportedException($"Can not translate field '{propAnnotation.MemberInfo}' to Javascript"); } if (containsObservables) { node.AddAnnotation(ResultIsObservableAnnotation.Instance); if (ViewModelJsonConverter.IsCollection(propertyType)) { node.AddAnnotation(ResultIsObservableArrayAnnotation.Instance); } } node.AddAnnotation(new ViewModelInfoAnnotation(propertyType, containsObservables: containsObservables)); node.AddAnnotation(MayBeNullAnnotation.Instance); } if (node.Annotation <ViewModelInfoAnnotation>() is var vmAnnotation && vmAnnotation?.Type != null && vmAnnotation.SerializationMap == null) { vmAnnotation.SerializationMap = mapper.GetMap(vmAnnotation.Type); } }
private string SetProperty(string target, PropertyInfo property, string value) { if (ViewModelJsonConverter.IsPrimitiveType(property.PropertyType)) { return(target + "." + property.Name + "(" + value + ")"); } else { return($"dotvvm.serialization.deserialize({ value }, { target }.{ property.Name })"); } }
/// <summary> /// Builds the view model for the client. /// </summary> public void BuildViewModel(DotvvmRequestContext context) { // serialize the ViewModel var serializer = CreateJsonSerializer(); var viewModelConverter = new ViewModelJsonConverter(context.IsPostBack) { UsedSerializationMaps = new HashSet <ViewModelSerializationMap>() }; serializer.Converters.Add(viewModelConverter); var writer = new JTokenWriter(); serializer.Serialize(writer, context.ViewModel); // persist CSRF token writer.Token["$csrfToken"] = context.CsrfToken; // persist encrypted values if (viewModelConverter.EncryptedValues.Count > 0) { writer.Token["$encryptedValues"] = viewModelProtector.Protect(viewModelConverter.EncryptedValues.ToString(Formatting.None), context); } // serialize validation rules var validationRules = SerializeValidationRules(viewModelConverter); // create result object var result = new JObject(); result["viewModel"] = writer.Token; result["url"] = context.OwinContext.Request.Uri.PathAndQuery; result["virtualDirectory"] = DotvvmMiddleware.GetVirtualDirectory(context.OwinContext); if (context.IsPostBack || context.IsSpaRequest) { result["action"] = "successfulCommand"; var renderedResources = new HashSet <string>(context.ReceivedViewModelJson?["renderedResources"]?.Values <string>() ?? new string[] { }); result["resources"] = BuildResourcesJson(context, rn => !renderedResources.Contains(rn)); } else { result["renderedResources"] = JArray.FromObject(context.ResourceManager.RequiredResources); } // TODO: do not send on postbacks if (validationRules.Count > 0) { result["validationRules"] = validationRules; } context.ViewModelJson = result; }
/// <summary> /// Serializes the validation rules. /// </summary> private JObject SerializeValidationRules(ViewModelJsonConverter viewModelConverter) { var validationRules = new JObject(); foreach (var map in viewModelConverter.UsedSerializationMaps) { var rule = new JObject(); foreach (var property in map.Properties.Where(p => p.ClientValidationRules.Any())) { rule[property.Name] = JToken.FromObject(property.ClientValidationRules); } if (rule.Count > 0) { validationRules[map.Type.ToString()] = rule; } } return(validationRules); }
/// <summary> /// Builds the view model for the client. /// </summary> public void BuildViewModel(DotvvmRequestContext context, DotvvmView view) { // serialize the ViewModel var serializer = CreateJsonSerializer(); var viewModelConverter = new ViewModelJsonConverter() { EncryptedValues = new JArray(), UsedSerializationMaps = new HashSet<ViewModelSerializationMap>() }; serializer.Converters.Add(viewModelConverter); var writer = new JTokenWriter(); serializer.Serialize(writer, context.ViewModel); // persist CSRF token writer.Token["$csrfToken"] = context.CsrfToken; // persist encrypted values if (viewModelConverter.EncryptedValues.Count > 0) writer.Token["$encryptedValues"] = viewModelProtector.Protect(viewModelConverter.EncryptedValues.ToString(Formatting.None), context); // serialize validation rules var validationRules = SerializeValidationRules(viewModelConverter); // create result object var result = new JObject(); result["viewModel"] = writer.Token; result["url"] = context.OwinContext.Request.Uri.PathAndQuery; result["virtualDirectory"] = DotvvmMiddleware.GetVirtualDirectory(context.OwinContext); if (context.IsPostBack || context.IsSpaRequest) { result["action"] = "successfulCommand"; var renderedResources = new HashSet<string>(context.ReceivedViewModelJson?["renderedResources"]?.Values<string>() ?? new string[] { }); result["resources"] = BuildResourcesJson(context, rn => !renderedResources.Contains(rn)); } else { result["renderedResources"] = JArray.FromObject(context.ResourceManager.RequiredResources); } // TODO: do not send on postbacks if (validationRules.Count > 0) result["validationRules"] = validationRules; context.ViewModelJson = result; }
public static bool IsComplexType(this JsExpression expr) { if (expr.TryGetAnnotation <ViewModelInfoAnnotation>(out var vmInfo)) { return(ViewModelJsonConverter.IsComplexType(vmInfo.Type)); } if (expr is JsAssignmentExpression assignment && assignment.Operator == null) { return(IsComplexType(assignment.Right)); } if (expr is JsBinaryExpression binary && (binary.Operator == BinaryOperatorType.ConditionalAnd || binary.Operator == BinaryOperatorType.ConditionalOr)) { return(IsComplexType(binary.Left) && IsComplexType(binary.Right)); } if (expr is JsConditionalExpression conditional) { return(IsComplexType(conditional.TrueExpression) && IsComplexType(conditional.FalseExpression)); } if (expr is JsLiteral literal) { return(literal.Value != null && ViewModelJsonConverter.IsComplexType(literal.Value.GetType())); } return(false); }
/// <summary> /// Populates the view model from Json sent. /// </summary> /// <param name="state">The state.</param> /// <param name="serializedPostData">The serialized post data.</param> /// <returns></returns> /// <exception cref="Exception"></exception> public virtual string PopulateViewModel(ViewModelState state, string serializedPostData) { var data = state.LastSentViewModelJson = JObject.Parse(serializedPostData); var viewModelToken = (JObject)data["viewModel"]; state.CsrfToken = viewModelToken["$csrfToken"].Value <string>(); var viewModelConverter = new ViewModelJsonConverter(true, Mapper); var serializer = CreateJsonSerializer(); serializer.Converters.Add(viewModelConverter); try { viewModelConverter.Populate(viewModelToken.CreateReader(), serializer, state.LastSentViewModel); } catch (Exception ex) { throw new Exception( $"Could not deserialize viewModel of type {state.LastSentViewModel.GetType().Name}. {GeneralViewModelRecommendations}", ex); } return((string)data["taskId"]); }
/// <summary> /// Populates the view model from the data received from the request. /// </summary> /// <returns></returns> public void PopulateViewModel(DotvvmRequestContext context, DotvvmView view, string serializedPostData) { var viewModelConverter = new ViewModelJsonConverter(); // get properties var data = context.ReceivedViewModelJson = JObject.Parse(serializedPostData); var viewModelToken = (JObject)data["viewModel"]; // load CSRF token context.CsrfToken = viewModelToken["$csrfToken"].Value<string>(); if (viewModelToken["$encryptedValues"] != null) { // load encrypted values var encryptedValuesString = viewModelToken["$encryptedValues"].Value<string>(); viewModelConverter.EncryptedValues = JArray.Parse(viewModelProtector.Unprotect(encryptedValuesString, context)); } else viewModelConverter.EncryptedValues = new JArray(); // get validation path context.ModelState.ValidationTargetPath = data["validationTargetPath"].Value<string>(); // populate the ViewModel var serializer = CreateJsonSerializer(); serializer.Converters.Add(viewModelConverter); viewModelConverter.Populate(viewModelToken, serializer, context.ViewModel); }
/// <summary> /// Serializes the validation rules. /// </summary> private JObject SerializeValidationRules(ViewModelJsonConverter viewModelConverter) { var validationRules = new JObject(); foreach (var map in viewModelConverter.UsedSerializationMaps) { var rule = new JObject(); foreach (var property in map.Properties.Where(p => p.ClientValidationRules.Any())) { rule[property.Name] = JToken.FromObject(property.ClientValidationRules); } if (rule.Count > 0) validationRules[map.Type.ToString()] = rule; } return validationRules; }
/// <summary> /// Validates the view model. /// </summary> private IEnumerable <ViewModelValidationError> ValidateViewModel(object viewModel, string pathPrefix, HashSet <object> alreadyValidated) { if (alreadyValidated.Contains(viewModel)) { yield break; } if (viewModel == null) { yield break; } var viewModelType = viewModel.GetType(); if (ViewModelJsonConverter.IsPrimitiveType(viewModelType) || ViewModelJsonConverter.IsNullableType(viewModelType)) { yield break; } alreadyValidated.Add(viewModel); if (ViewModelJsonConverter.IsEnumerable(viewModelType)) { if (pathPrefix.Length == 0) { pathPrefix = "$data"; } else { pathPrefix += "()"; } // collections var index = 0; foreach (var item in (IEnumerable)viewModel) { foreach (var error in ValidateViewModel(item, pathPrefix + "[" + index + "]()", alreadyValidated)) { yield return(error); } index++; } yield break; } // validate all properties on the object var map = viewModelSerializationMapper.GetMap(viewModel.GetType()); foreach (var property in map.Properties.Where(p => p.TransferToServer)) { var value = property.PropertyInfo.GetValue(viewModel); var path = CombinePath(pathPrefix, property.Name); // validate the property if (property.ValidationRules.Any()) { var context = new ValidationContext(viewModel, validationItems) { MemberName = property.Name }; foreach (var rule in property.ValidationRules) { var propertyResult = rule.SourceValidationAttribute?.GetValidationResult(value, context); if (propertyResult != ValidationResult.Success) { yield return(new ViewModelValidationError() { PropertyPath = path, ErrorMessage = rule.ErrorMessage }); } } } // inspect objects if (value != null) { if (ViewModelJsonConverter.IsComplexType(property.Type)) { // complex objects foreach (var error in ValidateViewModel(value, path, alreadyValidated)) { yield return(error); } } } } if (viewModel is IValidatableObject) { foreach (var error in ((IValidatableObject)viewModel).Validate( new ValidationContext(viewModel, validationItems))) { var paths = new List <string>(); if (error.MemberNames != null) { foreach (var memberName in error.MemberNames) { paths.Add(CombinePath(pathPrefix, memberName)); } } if (!paths.Any()) { paths.Add(pathPrefix); } foreach (var memberPath in paths) { yield return(new ViewModelValidationError() { PropertyPath = memberPath, ErrorMessage = error.ErrorMessage }); } } } }