/// <summary> /// Processes the HTML element that represents a new object. /// </summary> private ResolvedControl ProcessObjectElement(DothtmlElementNode element, DataContextStack dataContext) { object[] constructorParameters; var controlMetadata = controlResolver.ResolveControl(element.TagPrefix, element.TagName, out constructorParameters); var control = new ResolvedControl(controlMetadata, element, dataContext) { ContructorParameters = constructorParameters }; var dataContextAttribute = element.Attributes.FirstOrDefault(a => a.AttributeName == "DataContext"); if (dataContextAttribute != null) { ProcessAttribute(dataContextAttribute, control, dataContext); } if (control.Properties.ContainsKey(DotvvmBindableObject.DataContextProperty) && control.Properties[DotvvmBindableObject.DataContextProperty] is ResolvedPropertyBinding) { dataContext = new DataContextStack( ((ResolvedPropertyBinding)control.Properties[DotvvmBindableObject.DataContextProperty]).Binding.GetExpression().Type, dataContext); control.DataContextTypeStack = dataContext; } if (controlMetadata.DataContextConstraint != null && !controlMetadata.DataContextConstraint.IsAssignableFrom(dataContext.DataContextType)) { throw new DotvvmCompilationException($"The control '{controlMetadata.Name}' requires a DataContext of type '{controlMetadata.DataContextConstraint.FullName}'!", element.Tokens); } // set properties from attributes foreach (var attribute in element.Attributes.Where(a => a.AttributeName != "DataContext")) { ProcessAttribute(attribute, control, dataContext); } var typeChange = DataContextChangeAttribute.GetDataContextExpression(dataContext, control); if (typeChange != null) { dataContext = new DataContextStack(typeChange, dataContext); } ProcessControlContent(element.Content, control); // check required properties var missingProperties = control.Metadata.Properties.Values.Where(p => p.MarkupOptions.Required && !control.Properties.ContainsKey(p)); if (missingProperties.Any()) { throw new DotvvmCompilationException($"The control '{ control.Metadata.Name }' is missing required properties: { string.Join(", ", missingProperties.Select(p => "'" + p.Name + "'")) }.", control.DothtmlNode.Tokens); } return(control); }
/// <summary> /// Processes the element which contains property value. /// </summary> private ResolvedPropertySetter ProcessElementProperty(ResolvedControl control, DotvvmProperty property, IEnumerable <DothtmlNode> elementContent) { var dataContext = control.DataContextTypeStack; var typeChange = DataContextChangeAttribute.GetDataContextExpression(dataContext, control, property); if (typeChange != null) { dataContext = new DataContextStack(typeChange, dataContext); } // the element is a property if (IsTemplateProperty(property)) { // template return(new ResolvedPropertyTemplate(property, ProcessTemplate(elementContent, dataContext))); } else if (IsCollectionProperty(property)) { // collection of elements var collection = FilterNodes <DothtmlElementNode>(elementContent, property) .Select(childObject => ProcessObjectElement(childObject, dataContext)); return(new ResolvedPropertyControlCollection(property, collection.ToList())); } else if (property.PropertyType == typeof(string)) { // string property var strings = FilterNodes <DothtmlLiteralNode>(elementContent, property); var value = string.Concat(strings.Select(s => s.Value)); return(new ResolvedPropertyValue(property, value)); } else if (IsControlProperty(property)) { // new object var children = FilterNodes <DothtmlElementNode>(elementContent, property).ToList(); if (children.Count > 1) { throw new DotvvmCompilationException($"The property '{property.MarkupOptions.Name}' can have only one child element!"); } else if (children.Count == 1) { return(new ResolvedPropertyControl(property, ProcessObjectElement(children[0], dataContext))); } else { return(new ResolvedPropertyControl(property, null)); } } else { throw new DotvvmCompilationException($"The property '{property.FullName}' is not supported!"); } }
/// <summary> /// Processes the HTML attribute. /// </summary> private void ProcessAttribute(DothtmlAttributeNode attribute, ResolvedControl control, DataContextStack dataContext) { if (attribute.AttributePrefix == "html") { if (!control.Metadata.HasHtmlAttributesCollection) { throw new DotvvmCompilationException($"control { control.Metadata.Name } does not have html attribute collection", attribute.Tokens); } control.SetHtmlAttribute(attribute.AttributeName, ProcessAttributeValue(attribute.ValueNode, dataContext)); return; } if (!string.IsNullOrEmpty(attribute.AttributePrefix)) { throw new DotvvmCompilationException("Attributes with XML namespaces are not supported!", attribute.Tokens); } // find the property var property = FindProperty(control.Metadata, attribute.AttributeName); if (property != null) { if (property.IsBindingProperty) { var typeChange = DataContextChangeAttribute.GetDataContextExpression(dataContext, control, property); if (typeChange != null) { dataContext = new DataContextStack(typeChange, dataContext); } } if (!property.MarkupOptions.MappingMode.HasFlag(MappingMode.Attribute)) { throw new DotvvmCompilationException($"The property '{ property.FullName }' cannot be used as attribute", attribute.Tokens); } // set the property if (attribute.ValueNode == null) { throw new DotvvmCompilationException($"The attribute '{property.Name}' on the control '{control.Metadata.Name}' must have a value!", attribute.Tokens); } else if (attribute.ValueNode is DothtmlValueBindingNode) { // binding var bindingNode = (attribute.ValueNode as DothtmlValueBindingNode).BindingNode; if (!property.MarkupOptions.AllowBinding) { throw new DotvvmCompilationException($"The property '{ property.FullName }' cannot contain binding.", bindingNode.Tokens); } var resolvedBinding = ProcessBinding(bindingNode, dataContext); control.SetProperty(new ResolvedPropertyBinding(property, resolvedBinding)); } else { // hard-coded value in markup if (!property.MarkupOptions.AllowHardCodedValue) { throw new DotvvmCompilationException($"The property '{ property.FullName }' cannot contain hard coded value.", attribute.ValueNode.Tokens); } var textValue = attribute.ValueNode as DothtmlValueTextNode; var value = ReflectionUtils.ConvertValue(textValue.Text, property.PropertyType); control.SetPropertyValue(property, value); } } else if (control.Metadata.HasHtmlAttributesCollection) { // if the property is not found, add it as an HTML attribute control.SetHtmlAttribute(attribute.AttributeName, ProcessAttributeValue(attribute.ValueNode, dataContext)); } else { throw new DotvvmCompilationException($"The control '{control.Metadata.Type}' does not have a property '{attribute.AttributeName}' and does not allow HTML attributes!"); } }