I have created a custom configuration section in a c# class library by inheriting from ConfigurationSection. I reference the class library in my web application (also c#, ASP.NET), fill in the appropriate attributes and everything works great. The problem starts when I start adding validators.
For example, this property:
[ConfigurationProperty("appCode", IsRequired = true)]
public string ApplicationCode
{
get
{
return (string)base["appCode"];
}
set
{
base["appCode"] = value;
}
}
As is it works fine, but as soon as I add this:
[StringValidator(MinLength=1)]
It bombs with the following error:
The value for the property 'appCode' is not valid. The error is: The string must be at least 1 characters long.
I get this error even though a valid appCode value is in my web.config file. If I remove the validator it works perfectly. Does anyone know how to get around this?
I was able to work around this issue by using an explicit ConfigurationProperty as the key to my properties collection rather than a string, as per the following implementation:
public class AssemblyElement : ConfigurationElement
{
private static readonly ConfigurationProperty _propAssembly;
private static readonly ConfigurationPropertyCollection _properties;
static AssemblyElement()
{
_propAssembly = new ConfigurationProperty("assembly", typeof(string), null, null, new StringValidator(1), ConfigurationPropertyOptions.IsKey | ConfigurationPropertyOptions.IsRequired);
_properties = new ConfigurationPropertyCollection();
_properties.Add(_propAssembly);
}
internal AssemblyElement() { }
public AssemblyElement(string assemblyName)
{
this.Assembly = assemblyName;
}
[ConfigurationProperty("assembly", IsRequired = true, IsKey = true, DefaultValue = "")]
[StringValidator(MinLength = 1)]
public string Assembly
{
get { return (string)base[_propAssembly]; }
set { base[_propAssembly] = value; }
}
internal AssemblyName AssemblyName
{
get { return new AssemblyName(this.Assembly); }
}
protected override ConfigurationPropertyCollection Properties
{
get { return _properties; }
}
}
(This code is closely modeled after the code reflected from the AssemblyInfo configuration element class. I still wish I didn't have to duplicate my validations, but at least this code allows me to specify a blank default value while still requiring a value to be entered.)
Seems like the answer is indeed because they don't have a default value. Seems odd, so if someone has a better answer let me know and I'll accept theirs.
I had this problem for a while, then I realized that the validators are not for making attribute or elements required, they are for validating them.
To make a property required you need to use the IsRequired and ConfigrationPropertyOptions.IsRequired, e.g.
[ConfigurationProperty("casLogoutUrl", DefaultValue = null, IsRequired = true, Options = ConfigurationPropertyOptions.IsRequired)]
[StringValidator(MinLength=10)]
Or (if using the api)
ConfigurationProperty casLoginUrl = new ConfigurationProperty("casLoginUrl", typeof(string), null, null, new StringValidator(1), ConfigurationPropertyOptions.IsRequired);
Doing this, the Configuration framework will handle the property being required itself, and the validator handles validating what's in the value. Validators are not meant for making something required.
This also works on elements to make child elements required. E.g. if you are making a custom ConfigSection with child elements and need a child element to be required. However, if you make a CustomValidator that inherits from ConfigurationValidatorBase, you need to make use of ElementInformation.IsPresent, e.g.
public override void Validate(object value)
{
CredentialConfigurationElement element = (CredentialConfigurationElement)value;
if (!element.ElementInformation.IsPresent)
return; //IsRequired is handle by the framework, don't throw error here only throw an error if the element is present and it fails validation.
if (string.IsNullOrEmpty(element.UserName) || string.IsNullOrEmpty(element.Password))
throw new ConfigurationErrorsException("The restCredentials element is missing one or more required Attribute: userName or password.");
}
Long story short, you are missing the options part of your attribute to make it required and shouldn't use StringValidator(MinLength=1) to make it required. In fact StringValidator(MinLength=1) is completely redundant. If you make it required it's impossible for MinLength=1 to fail without the Required failing first because if it's present, it's guaranteed to be at least 1 character long.
Change your validator to
[ConfigurationProperty("appCode", IsRequired = true, Options=ConfigurationPropertyOptions.IsRequired)]
Then ditch the string validator.
The resolving of the StringValidator can be done by any one of the following:
Removing MinLength argument
Setting MinLength = 0
Removing the StringValidator Attribute
Adding DefaultValue to the ConfigurationProperty Attribute
The Ideal definition for the property is like:
[ConfigurationProperty("title", IsRequired = true, DefaultValue = "something")]
[StringValidator(InvalidCharacters = "~!##$%^&*()[]{}/;’\"|\\"
, MinLength = 1
, MaxLength = 256)]
public string Title
{
get { return this["title"] as string; }
set { this["title"] = value; }
}
Related
I'm using xsd2code for generating classes from schema. how ever I'm suspicious that auto properties can validate values or not?
because if I enable AutomaticProperties in xsd2code here is what I get for member with Regex restriction.
[System.Xml.Serialization.XmlAttributeAttribute(DataType="token", AttributeName="color")]
[System.ComponentModel.DataAnnotations.RegularExpressionAttribute("#[\\dA-F]{6}([\\dA-F][\\dA-F])?")]
public string Color { get; set; }
And when AutomaticProperties is disabled
[System.Xml.Serialization.XmlAttributeAttribute(DataType="token", AttributeName="color")]
[System.ComponentModel.DataAnnotations.RegularExpressionAttribute("#[\\dA-F]{6}([\\dA-F][\\dA-F])?")]
public string Color
{
get
{
return this._color;
}
set
{
System.ComponentModel.DataAnnotations.ValidationContext validatorPropContext = new System.ComponentModel.DataAnnotations.ValidationContext(this, null, null);
validatorPropContext.MemberName = "Color";
Validator.ValidateProperty(value, validatorPropContext);
this._color = value;
}
}
It seems these are not equivalent. so I think its a bug in xsd2code or maybe I'm misunderstanding something. whats the purpose of second generated code?
I thought RegularExpressionAttribute would also validate automatic properties too.
I'm configuring MvcSiteMapProvider with C# annotations and not through XML. I implemented a custom visibility provider based on the documentation. I derive my class from FilteredSiteMapNodeVisibilityProvider:
public class CustomVisibilityProvider: FilteredSiteMapNodeVisibilityProvider
{
public override bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
{
if (node.Attributes.Keys.Contains("customVisibility"))
{
string customVisibility = (string)node.Attributes["customVisibility"];
if (!string.IsNullOrEmpty(customVisibility))
{
customVisibility = customVisibility.Trim();
...
var criteria = ...
return criteria && base.IsVisible(node, sourceMetadata);
}
}
return base.IsVisible(node, sourceMetadata);
}
}
My Controller's view:
[MvcSiteMapNode(Title = "My View", ParentKey = "ParentController", Key = "MyView", Order = 922, PreservedRouteParameters = "id", Attributes = #"{ ""Visibility"": ""SiteMapPathHelper,!*"" }")]
public ActionResult MyView(int? id)
{
return ViewForEntity(id);
}
As we can see I didn't use my own customVisibility attribute with this view, but I'd like to use the standard Visibility attributes. This particular view shouldn't appear in the menu or elsewhere, except in the SiteMap.
The problem is that when this view's SiteMapNode is examined for visibility in the menu (a.k.a. (string)sourceMetadata["HtmlHelper"] == "MvcSiteMapProvider.Web.Html.SiteMapPathHelper"), the base.IsVisible(node, sourceMetadata) returns true. I'd expect that the FilteredSiteMapNodeVisibilityProvider will handle the Visibility attribute and return false, seeing that this view should only appear in the SiteMap.
As a workaround I currently implemented my own check:
private bool checkDefaultVisibility(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
{
bool defaultVisibility = sourceMetadata["HtmlHelper"] == null || !node.Attributes.Keys.Contains("Visibility");
if (sourceMetadata["HtmlHelper"] != null && node.Attributes.Keys.Contains("Visibility"))
{
var htmlHelper = (string)sourceMetadata["HtmlHelper"]; // Example: "MvcSiteMapProvider.Web.Html.SiteMapPathHelper"
var helpersRules = ((string)node.Attributes["Visibility"]).Split(',');
foreach(var helperRule in helpersRules)
{
if (helperRule != "!*" && htmlHelper.EndsWith("." + helperRule))
{
defaultVisibility = true;
break;
}
}
}
return defaultVisibility;
}
This is a method of my custom visibility provider. I hate it, because it's not universal, only handles specific cases. At the same time I don't want to reinvent the wheel here. I want Visibility to be handled by the MvcSiteMapProvider internals. How to achieve that?
Dictionary keys are case sensitive. The reason why FilteredSiteMapVisibilityProvider is not returning false is because you have set an attribute named Visibility instead of the expected name visibility.
[MvcSiteMapNode(Title = "My View", ParentKey = "ParentController", Key = "MyView", Order = 922, PreservedRouteParameters = "id", Attributes = #"{ ""visibility"": ""SiteMapPathHelper,!*"" }")]
As far as making visibility "universal", that would be difficult to do since everyone has different visibility requirements. The purpose of visibility providers is to implement your own visibility requirements. FilteredSiteMapNodeVisibilityProvider is about as generic as it can get, and if that doesn't meet your requirements you need to go custom.
Do note you can override the default visibility provider for specific nodes by setting the VisibilityProvider to the type string of the custom visibility provider class.
[MvcSiteMapNode(Title = "My View", ParentKey = "ParentController", Key = "MyView", Order = 922, PreservedRouteParameters = "id", VisibilityProvider = "MyNamespace.CustomVisibilityProvider, MyAssembly", Attributes = #"{ ""customVisibility"": ""foo"" }")]
If you need to use more than one visibility provider on the same node, it can be done using external DI and the CompositeSiteMapNodeVisibilityProvider as shown here.
Note that you can inherit this class for use with the internal DI container if you need to - but for internal DI you need a default constructor, so the types that it uses internally must be hard-coded into the constructor. But you can create as many of these classes as you need for your entire visibility configuration.
using MvcSiteMapProvider;
using MvcSiteMapProvider.Reflection;
public class MyCompositeVisibilityProvider : CompositeSiteMapNodeVisibilityProvider
{
public MyCompositeVisibilityProvider()
: base(
typeof(MyCompositeVisibilityProvider).ShortAssemblyQualifiedName(),
// Note that the visibility providers are executed in
// the order specified here, but execution stops when
// the first visibility provider returns false.
new FilteredSiteMapNodeVisibilityProvider(),
new TrimEmptyGroupingNodesVisibilityProvider(),
new CustomVisibilityProvider()
)
{ }
}
And then calling it by using:
[MvcSiteMapNode(Title = "My View", ParentKey = "ParentController", Key = "MyView", Order = 922, PreservedRouteParameters = "id", VisibilityProvider = "MyNamespace.MyCompositeVisibilityProvider, MyAssembly", Attributes = #"{ ""visibility"": ""SiteMapPathHelper,!*"", ""customVisibility"": ""foo"" }")]
Also note there are many other ways to control visibility including security trimming, customizing the templates (or creating new templates and specifying the templateName in the HTML helper explicitly) in the /Views/Shared/DisplayTemplates/ folder, or even creating custom HTML helpers.
I'm trying to use XAML to serialize/deserialize some custom (non-WPF/UI) information, and would like to enforce that certain properties are required.
XAML deserialization, by default, just creates each object with the default constructor, and then sets any properties it finds in the element's attributes, or in property element syntax. Any properties of the underlying object which are not specified in the XAML being serialized are just left as they are, i.e. whatever value they got after construction.
I'd like to know the best way of specifying that a certain property must be present in the XAML - and if not, have the deserialization fail.
I was expecting an attribute of some kind, but I can't find anything.
There are certain types in WPF which do exhibit this behaviour, but presumably WPF uses its own custom way of enforcing this. For example if you have..
<Setter Property="Height" ></Setter>
..the designer will complain 'The property "Value" is missing'.
I can think of some pretty convoluted ways of doing this:
Have each property setter somehow record it was called, and then have custom code run after deserialization which checks that all 'required' properties were actually set.
Use nullable properties everywhere, and then check after deserialization if any 'required' ones are still null. (Of course this won't work if null is a valid thing to set something to!)
Perhaps there's a way of writing a custom XamlObjectWriter which can check for some [Required] attribute on the object's properties, and fail if these are not found by the XamlReader.
These all sound like a lot more work than I hoped - and I'm sure there's a more obvious way. Does anyone have any other ideas, or experience of (and maybe a solution to) this problem?
I know this is old, but I ran across the question while looking for a way to do this shortly before I figured it out. Maybe it'll help someone else. Fortunately this is easy: implement ISupportInitialize. I'm using WPF but it should work anywhere.
public class Hero : ISupportInitialize
{
public string Who
{ get; set; } = string.Empty;
public string What
{ get; set; } = string.Empty;
public string Where
{ get; set; } = string.Empty;
public void BeginInit()
{
// set a flag here if your property setters
// have dependencies on other properties
}
public void EndInit()
{
if (string.IsNullOrWhiteSpace(Who))
throw new Exception($"The property \"Who\" is missing");
if (string.IsNullOrWhiteSpace(What))
throw new Exception($"The property \"What\" is missing");
// Where is optional...
}
}
Who and What are required, but What is missing on the second entry:
<Window.Resources>
<local:Hero x:Key="Badguy" Who="Vader" What="Sith" Where="Death Star"/>
<local:Hero x:Key="Goodguy" Who="HanSolo" Where="Millenium Falcon"/>
</Window.Resources>
In the VS2017 XAML markup editor:
I was facing a similar problem recently. After not being able to find any straightforward way, I decided to hook into the events of XamlObjectWriter to add custom support for this. It was basically what you suggested in point 3, except it turned out not really that complicated.
Basically it works like this: a dictionary is kept where each deserialized object is mapped to a set of remaining required properties. The BeforePropertiesHandler fills this set for the current object with all its properties with the RequiredAttribute. The XamlSetValueHandler removes the current property from the set. Finally, the AfterPropertiesHandler makes sure that there are no required properties left not set on the current object and throws an exception otherwise.
class RequiredAttribute : Attribute
{
}
public T Deserialize<T>(Stream stream)
{
var requiredProperties = new Dictionary<object, HashSet<MemberInfo>>();
var writerSettings = new XamlObjectWriterSettings
{
BeforePropertiesHandler = (sender, args) =>
{
var thisInstanceRequiredProperties = new HashSet<MemberInfo>();
foreach(var propertyInfo in args.Instance.GetType().GetProperties())
{
if(propertyInfo.GetCustomAttribute<RequiredAttribute>() != null)
{
thisInstanceRequiredProperties.Add(propertyInfo);
}
}
requiredProperties[args.Instance] = thisInstanceRequiredProperties;
},
XamlSetValueHandler = (sender, args) =>
{
if(!requiredProperties.ContainsKey(sender))
{
return;
}
requiredProperties[sender].Remove(args.Member.UnderlyingMember);
},
AfterPropertiesHandler = (sender, args) =>
{
if(!requiredProperties.ContainsKey(args.Instance))
{
return;
}
var propertiesNotSet = requiredProperties[args.Instance];
if(propertiesNotSet.Any())
{
throw new Exception("Required property \"" + propertiesNotSet.First().Name + "\" not set.");
}
requiredProperties.Remove(args.Instance);
}
};
var readerSettings = new XamlXmlReaderSettings
{
LocalAssembly = Assembly.GetExecutingAssembly(),
ProvideLineInfo = true
};
using(var reader = new XamlXmlReader(stream, readerSettings))
using(var writer = new XamlObjectWriter(reader.SchemaContext, writerSettings))
{
XamlServices.Transform(reader, writer);
return (T)writer.Result;
}
}
My model has an property whcih I assigned a ReadOnly tag. My intention is to set a propery to readonly true or false depending of a condition like
class Test {
static bool test() { // It is my model
// My logical response ...
return true;
}
[ReadOnly(test)]
datetime prop {get; set;}
}
using this model I get the error message:
Error 7 An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter
Could you, pleaee, get me an idea for this?
=====================================================================================
Solution like answer 3:
Inside Template:
cshtml:
...
#if (Model.condition) {
<td>#Html.EditorFor(m => m.prop)</td>
} else {
<td>#Html.DisplayFor(m => m.prop)</td>
}
...
It will be inside the template.
Inside Model in the copmnstructor I set the condition of the property condition:
class XX {
public condition { get; set; } // not necessary readonly, I do the logical inside the template.
public datetime prop {get; set; }
public XX (bool _condition) {
condition = _condition;
}
}
You cannot use something other than described in the error message as the parameter for attributes.
It is a sad true, but still a true.
Only this:
[ReadOnly(5)]
[ReadOnly("string")] // Or other type (int/long/byte/etc..) which can be used with const keyword.
[ReadOnly(Enums.SomeValue)]
[ReadOnly(typeof(SomeType))]
[ReadOnly(new string[] { "array", "of", "strings"} )]
So unfortunately, you wont succeed making custom parameter type:
class ReadOnlyAttribute { ReadOnlyAttribute(MyClass foo) { ... } }
One alternative would be to do it within the get/set, something like:
class test
{
static bool test() {
...
}
private datetime prop;
public datetime Prop
{
get { return prop; }
set
{
if (test)
prop = value;
else
// Ignore, throw exception, etc.
}
}
}
The metadata for the model (which includes your IsReadOnly) is created by the Model Metadata providers. This providers only have information about data types, and property names, but not about the concrete values of the properties of an instance of the model. So the metadata can not depend on the value of a property or method of the model class. (So implementing a Custom ModelMetada Provider wouldn't solve your problem).
Then, you have to find an alternative, hacky, way to do it:
Create a view model with two properties, the original, without the readonly attribute and an additional readonly property, decorated with the readonly attribute.
In the view, decide which of the two to show.
public class MyModel
{
public DateTime MyProperty { get; set;}
[ReadOnly]
public DateTime MyPropertyRo { get; set;}
}
If you want to recover the posted values, the editable version should use the original property in the Telerik control. The non-editable version should use the readonly property in the Telerik control, and the original property in a hidden-field, so that you can recover it in the post.
#if (Model.MyPropertyIsReadOnly)
{
#Html.HiddenFor(m => m.Property)
#Html.TelerikEditorFor(m => m.PropertyRo ...)
}
else
{
#Html.TelerikEditorFor(m => m.Property ...)
}
If you have to do this in many different views, you can create an Html helper (extension method for Html), which receives the 3 properties and includes the last sample code.
Finally, it would be even better to make a custom Editor template, but that's much harder to do if you don't have experience.
There is still another option: contact telerik, and ask them to implement a version of their control which receives a readonly parameter, and does this automatically for you. I think it must be really easy for them to implement it. So, if you're lucky enough...
Let's say I have a configuration property that looks like this. Note that there is no default value.
[ConfigurationProperty("x", IsRequired = true)]
[StringValidator(MinLength = 1)]
public string X
{
get { return (string)this["x"]; }
set { this["x"] = value; }
}
Now I add my section like this:
<mySection x="123" />
I'll get this error:
The value for the property 'x' is not
valid. The error is: The string must
be at least 1 characters long.
It works if I change the configuration property to include a default like this:
[ConfigurationProperty("x", DefaultValue="abc", IsRequired = true)]
[StringValidator(MinLength = 1)]
public string X
{
get { return (string)this["x"]; }
set { this["x"] = value; }
}
This implies that validator validates the default value even if IsRequired is true. It also means that I have to include a dummy default values on all my properties to pass validation even though they won't actually be used.
Is this just bad design or is there a valid reason for this behavior?
I have had this problem before. There was a valid reason for this but I cannot remember the details.
I cannot remember if this works but you can try declaring the property in the constructor where null is the default value.
public class CustomConfigurationSection : ConfigurationSection
{
public CustomConfigurationSection()
{
Properties.Add(new ConfigurationProperty(
"x",
typeof(string),
null,
null,
new StringValidator(1),
ConfigurationPropertyOptions.IsRequired));
}
public string X
{
get { return (string)this["x"]; }
set { this["x"] = value; }
}
}
This is related to using default values and validators but is where a default value is wanted.
http://msdn.microsoft.com/en-us/library/system.configuration.configurationproperty(VS.85).aspx#1
EDIT
I have just tried out the previous code and it does as I expected. My previous code did not compile as I missed out a constructor property so I have fixed that.
The reason is that the configuration section classes can be newed up in code without there being a configuration file. You can use a default constructor and not specify any values for the property. In this case, even if you have specified IsRequired=true there is no exception thrown. In other words, IsRequired only applies if the property is being deserialized from XML.
However, DefaultValue does apply in this case, as it does when the property is being deserialized from XML (as does any ConfigurationValidatorAttribute).
This makes sense if you are using configuration sections in unit testing. It's really nice to A) have a declarative default value when constructing the section and B) have the default value validated.
As per my understanding, this behavior is highly required.
Since the configuration is one of the core area of any application, and suppose, no value has been provided for an application critical property, then the whole application may lead to some unwanted behavior(could be a crash, indefinite resource utilization etc). I think that is the reason, most of the .Net inbuilt configuration properties like Session timeout etc. were set to a default value and they will be applied even user didn't specified value.
As said previously, this does not help in getting a validation without having to specify a default value for a property, which then makes the IsRequired property of the ConfigurationPropertyAttribute useless.