/// <summary> /// Gets the properties of the specified type. /// </summary> protected virtual IEnumerable <ViewModelPropertyMap> GetProperties(Type type) { foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name)) { if (property.GetCustomAttribute <JsonIgnoreAttribute>() != null) { continue; } var propertyMap = new ViewModelPropertyMap() { PropertyInfo = property, Name = property.Name, ViewModelProtection = ProtectMode.None, Type = property.PropertyType, TransferAfterPostback = property.GetMethod != null && property.GetMethod.IsPublic, TransferFirstRequest = property.GetMethod != null && property.GetMethod.IsPublic, TransferToServer = property.SetMethod != null && property.SetMethod.IsPublic, JsonConverter = GetJsonConverter(property), Populate = ViewModelJsonConverter.IsComplexType(property.PropertyType) && !ViewModelJsonConverter.IsEnumerable(property.PropertyType) && property.GetMethod != null }; foreach (ISerializationInfoAttribute attr in property.GetCustomAttributes().OfType <ISerializationInfoAttribute>()) { attr.SetOptions(propertyMap); } var bindAttribute = property.GetCustomAttribute <BindAttribute>(); if (bindAttribute != null) { propertyMap.Bind(bindAttribute.Direction); if (bindAttribute.Name != null) { propertyMap.Name = bindAttribute.Name; } } var viewModelProtectionAttribute = property.GetCustomAttribute <ProtectAttribute>(); if (viewModelProtectionAttribute != null) { propertyMap.ViewModelProtection = viewModelProtectionAttribute.Settings; } propertyMap.ClientExtenders = property.GetCustomAttributes <ClientExtenderAttribute>() .OrderBy(c => c.Order) .Select(extender => new ClientExtenderInfo() { Name = extender.Name, Parameter = extender.Parameter }) .ToList(); var validationAttributes = validationMetadataProvider.GetAttributesForProperty(property); propertyMap.ValidationRules = validationRuleTranslator.TranslateValidationRules(property, validationAttributes).ToList(); propertyMap.ValidateSettings(); yield return(propertyMap); } }
/// <summary> /// Gets the properties of the specified type. /// </summary> protected virtual IEnumerable <ViewModelPropertyMap> GetProperties(Type type) { foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name)) { if (property.GetCustomAttribute <JsonIgnoreAttribute>() != null) { continue; } var propertyMap = new ViewModelPropertyMap() { PropertyInfo = property, Name = property.Name, ViewModelProtection = ProtectMode.None, Type = property.PropertyType, TransferAfterPostback = property.GetMethod != null && property.GetMethod.IsPublic, TransferFirstRequest = property.GetMethod != null && property.GetMethod.IsPublic, TransferToServer = property.SetMethod != null && property.SetMethod.IsPublic, JsonConverter = GetJsonConverter(property), Populate = ViewModelJsonConverter.IsComplexType(property.PropertyType) && !ViewModelJsonConverter.IsEnumerable(property.PropertyType) && property.GetMethod != null }; var bindAttribute = property.GetCustomAttribute <BindAttribute>(); if (bindAttribute != null) { propertyMap.TransferAfterPostback = bindAttribute.Direction.HasFlag(Direction.ServerToClientPostback); propertyMap.TransferFirstRequest = bindAttribute.Direction.HasFlag(Direction.ServerToClientFirstRequest); propertyMap.TransferToServer = bindAttribute.Direction.HasFlag(Direction.ClientToServerNotInPostbackPath) || bindAttribute.Direction.HasFlag(Direction.ClientToServerInPostbackPath); propertyMap.TransferToServerOnlyInPath = !bindAttribute.Direction.HasFlag(Direction.ClientToServerNotInPostbackPath) && propertyMap.TransferToServer; } var viewModelProtectionAttribute = property.GetCustomAttribute <ProtectAttribute>(); if (viewModelProtectionAttribute != null) { propertyMap.ViewModelProtection = viewModelProtectionAttribute.Settings; } var clientExtenderList = property.GetCustomAttributes <ClientExtenderAttribute>(); if (clientExtenderList.Any()) { clientExtenderList .OrderBy(c => c.Order) .ToList() .ForEach(extender => propertyMap.ClientExtenders.Add(new ClientExtenderInfo() { Name = extender.Name, Parameter = extender.Parameter })); } var validationAttributes = validationMetadataProvider.GetAttributesForProperty(property); propertyMap.ValidationRules = validationRuleTranslator.TranslateValidationRules(property, validationAttributes).ToList(); yield return(propertyMap); } }
/// <summary> /// Populates the view model from the data received from the request. /// </summary> /// <returns></returns> public void PopulateViewModel(IDotvvmRequestContext context, string serializedPostData) { // get properties var data = context.ReceivedViewModelJson = JObject.Parse(serializedPostData); JObject viewModelToken; if (data["viewModelCacheId"] != null) { if (!context.Configuration.ExperimentalFeatures.ServerSideViewModelCache.IsEnabledForRoute(context.Route.RouteName)) { throw new InvalidOperationException("The server-side viewmodel caching is not enabled for the current route!"); } viewModelToken = viewModelServerCache.TryRestoreViewModel(context, (string)data["viewModelCacheId"], (JObject)data["viewModelDiff"]); data["viewModel"] = viewModelToken; } else { 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, viewModelMapper, context.Services, JObject.Parse(viewModelProtector.Unprotect(encryptedValuesString, context))); } else { viewModelConverter = new ViewModelJsonConverter(context.IsPostBack, viewModelMapper, context.Services); } // get validation path context.ModelState.ValidationTargetPath = data.SelectToken("additionalData.validationTargetPath")?.Value <string>(); // populate the ViewModel var serializer = CreateJsonSerializer(); serializer.Converters.Add(viewModelConverter); try { viewModelConverter.Populate(viewModelToken.CreateReader(), serializer, context.ViewModel); } catch (Exception ex) { throw new Exception($"Could not deserialize viewModel of type { context.ViewModel.GetType().Name }. {GeneralViewModelRecommendations}", ex); } }
/// <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, viewModelMapper) { UsedSerializationMaps = new HashSet<ViewModelSerializationMap>() }; serializer.Converters.Add(viewModelConverter); var writer = new JTokenWriter(); try { serializer.Serialize(writer, context.ViewModel); } catch (Exception ex) { throw new Exception($"Could not serialize viewModel of type { context.ViewModel.GetType().Name }. Serialization failed at property { writer.Path }. {GeneralViewModelRecomendations}", ex); } // 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.ResultIdFragment != null) { result["resultIdFragment"] = context.ResultIdFragment; } 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); }
public string BuildStaticCommandResponse(IDotvvmRequestContext context, object result) { var serializer = CreateJsonSerializer(); var viewModelConverter = new ViewModelJsonConverter(context.IsPostBack, viewModelMapper) { UsedSerializationMaps = new HashSet <ViewModelSerializationMap>() }; serializer.Converters.Add(viewModelConverter); var writer = new JTokenWriter(); try { serializer.Serialize(writer, result); } catch (Exception ex) { throw new Exception($"Could not serialize viewModel of type { context.ViewModel.GetType().Name }. Serialization failed at property { writer.Path }. {GeneralViewModelRecommendations}", ex); } return(writer.Token.ToString(JsonFormatting)); }
/// <summary> /// Populates the view model from the data received from the request. /// </summary> /// <returns></returns> public void PopulateViewModel(IDotvvmRequestContext 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, viewModelMapper, JObject.Parse(viewModelProtector.Unprotect(encryptedValuesString, context))); } else { viewModelConverter = new ViewModelJsonConverter(context.IsPostBack, viewModelMapper); } // get validation path context.ModelState.ValidationTargetPath = data["validationTargetPath"].Value <string>(); // populate the ViewModel var serializer = CreateJsonSerializer(); serializer.Converters.Add(viewModelConverter); try { viewModelConverter.Populate(viewModelToken, serializer, context.ViewModel); } catch (Exception ex) { throw new Exception($"Could not deserialize viewModel of type { context.ViewModel.GetType().Name }. {GeneralViewModelRecommendations}", ex); } }
/// <summary> /// Builds the view model for the client. /// </summary> public void BuildViewModel(IDotvvmRequestContext context) { // serialize the ViewModel var serializer = CreateJsonSerializer(); var viewModelConverter = new ViewModelJsonConverter(context.IsPostBack, viewModelMapper) { UsedSerializationMaps = new HashSet <ViewModelSerializationMap>() }; serializer.Converters.Add(viewModelConverter); var writer = new JTokenWriter(); try { serializer.Serialize(writer, context.ViewModel); } catch (Exception ex) { throw new Exception($"Could not serialize viewModel of type { context.ViewModel.GetType().Name }. Serialization failed at property { writer.Path }. {GeneralViewModelRecommendations}", ex); } // 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 bool useClientSideValidation = context.Configuration.ClientSideValidation; var validationRules = useClientSideValidation ? SerializeValidationRules(viewModelConverter) : null; // create result object var result = new JObject(); result["viewModel"] = writer.Token; result["url"] = context.HttpContext.Request.Url.PathAndQuery; result["virtualDirectory"] = context.HttpContext.Request.PathBase.Value?.Trim('/') ?? ""; if (context.ResultIdFragment != null) { result["resultIdFragment"] = context.ResultIdFragment; } 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> /// 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, viewModelMapper, JObject.Parse(viewModelProtector.Unprotect(encryptedValuesString, context))); } else viewModelConverter = new ViewModelJsonConverter(context.IsPostBack, viewModelMapper); // get validation path context.ModelState.ValidationTargetPath = data["validationTargetPath"].Value<string>(); // populate the ViewModel var serializer = CreateJsonSerializer(); serializer.Converters.Add(viewModelConverter); try { viewModelConverter.Populate(viewModelToken, serializer, context.ViewModel); } catch (Exception ex) { throw new Exception($"Could not deserialize viewModel of type { context.ViewModel.GetType().Name }. {GeneralViewModelRecomendations}", ex); } }
/// <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; }