I would like check no null parameters are assigned to record fields and let the properties have comments. I have found out the following arrangement does the trick for comments, but I lack ideas to check for null parameters without turning this record into a class.
So, a question: Is it possible to check during runtime that nulls won't be assigned to record fields? If so, how could one do it while still using records?
public record Test(string TestString)
{
/// <summary>
/// This is one way to get a comment on record properties. Are there others?
/// </summary>
public string TestString { get; init; } = TestString;
}
The code is also as a gist here.
This is an addition, but maybe helps with the accepted answer and the comment of different code.
It was because the code in the gist did a null check like
public record Test(string TestString)
{
/// <summary>
/// This is one way to get a comment on record properties. Are there others?
/// </summary>
public string TestString { get; init; } = TestString ?? throw new ArgumentNullException();
}
For a reason or another, I didn't include this pondering in the original question if there's maybe a shorter way. :)
<edit: The .NET 6 ArgumentNullException.ThrowIfNull(obj) could be handy here (or ThrowHelper).
You have to handle checks during inline assignment, as you've already discovered in your gist.
One possibility is to call an extension method to wrap any behavior.
public record Test(string TestString)
{
/// <summary>
/// This is one way to get a comment on record properties. Are there others?
/// </summary>
public string TestString { get; init; } = ValidationExtensions.Validate(TestString);
}
public static class ValidationExtensions
{
public static string Validate(string input)
{
if (string.IsNullOrEmpty(input))
throw new NullReferenceException();
return input;
}
}
This will correctly throw during initialization of the record:
var x = new Test(null);
Okay, so I am creating a Utility AI framework. For this to work I need a class that can change a lot depending on the situation I am sure, and I hope that there is a way to use polymorphism or some sort of design pattern to solve my issue.
Let me show you what I mean
I have an action for the sake of example let's say I have the following action Attack Target
This action can have a number of considerations that will vary a lot but all implement the same interface:
public interface IConsideration
{
/// <summary>
/// A unique named identifier for this consideration.
/// </summary>
string NameId { get; }
/// <summary>
/// The weight of this consideration.
/// </summary>
float Weight { get; set; }
/// <summary>
/// If true, then the output of the associated evaluator is inverted, in effect, inverting the
/// consideration.
/// </summary>
bool IsInverted { get; set; }
/// <summary>Calculates the utility given the specified context.</summary>
/// <param name="context">The context.</param>
/// <param name="value"></param>
/// <returns>The utility.</returns>
float Consider<T>(BaseAiContext context, T value);
}
The above is my current implementation it doesn't really solve the issue I have
As you can see the "most" important method of this interface is the Consider
and here lies the issue preferably I should be able to pass data to this class in a way that I can control.
For the sake of example let's say one consideration I have is Move To Location here I want to send the following parameters:
Location of target
Weapon type (ranged / melee)
location list
The above is just an example to prove my point. There is another issue with this - how can I pass the correct parameters when I finally have them? say that I have the following code:
public List<IConsideration> considerations;
float targetDistance = 2;
for (int i = 0; i < considerations.Count; i++)
{
float AxisScore = considerations[i].Consider(BaseAiContext,targetDistance );
}
Since I have to use the Interface type I am unable to know exactly which values to parse as parameters.
To sum it up:
How can i "parameterize" my class in a generic way?
How can I distinguish these parameterizations so I can provide a consideration with the correct values?
As #MarcRasmussen requested an example
As each implementation of your interface might consume different sets of arguments one way to solve it would be to kind of have key-value storage like a dictionary.
There are plenty of improvements to be made like, using ENUMS instead of strings, and having a static manager class for things like that to add/modify/remove settings.
This is a quick example and not tested, with the information available.
public class MoveToTarget : IConsideration
{
//method is changed to have generic return type and accept dictionary for settings
float Consider(BaseAiContext context, Dictionary<string,object> settings){
//make sure required keys exist
if(!settings.ContainsKey("DESTINATION"))
throw new ApplicationException("Missing key DESTINATION");
if(!settings.ContainsKey("SPEED"))
throw new ApplicationException("Missing key SPEED");
// retrieve you required settings, at this stage since you cast an object, you should check the type ... this problem would be solved if you have (as further below mentioned) a specific settings class for all your implementations. this way you ensure type safety too.
Point destination = (Point)settings["DESTINATION"];
float speed = (float)settings["SPEED"];
// and perform whatever logic you need. etc.
}
}
public List<IConsideration> considerations;
//this should be probably static and globally available (?) probably better to have a singleton manager class to deal with that.
Dictionary<string,object> Settings = new Dictionary<string,object>()
{
{"SPEED", 1.0f},
{"RANDOM", new Random()},
{"DESTINATION", new Point()},
{"XYZ", "XYZ"},
//etc.
}
//use foreach unless you need to have access to the index
foreach(var consideration in considerations)
{
float AxisScore = consideration.Consider(BaseAiContext, Settings);
}
some other improvements could be to have a specific settings class for each of your implementations like MoveToTargetSettings and then instead of havin ambiguous "KEYS" in a dictionary you can retrieve the specific settings by its class etc. like var settings = settingDictionary["MoveToTargetSettins"] as MoveToTargetSettings
I think for anything better more details are required, happy to discuss and answer any further questions outside of SO as that will be off-topic :)
This piece of code shows how to set and then read the data from the TempData object:
TempData["ErrorMessage"] = "some message";
var foo = TempData["ErrorMessage"];
If I have to use very often the TempData["ErrorMessage"] in my app, there is always a risk of making a typo in the key when developing new stuff.
I was thinking about a more secure way which would prevent this to happen, so I created a class:
/// <summary>
/// Contains a list of keys which are used in TempData across the controllers
/// </summary>
public static class TempDataKeys
{
/// <summary>
/// Stores the error message which is optionally dispalyed in the Shared/_ErrorMessage partial view defined in the _Layout.cshtml
/// </summary>
public const string ErrorMessage = "ErrorMessage";
}
and then set and read the data like this:
TempData[TempDataKeys.ErrorMessage] = "some message";
var foo = TempData[TempDataKeys.ErrorMessage];
Now I'm thinking if there is another way to achieve a similar thing by creation of an extension method for the TempData, so it would allow me to set and read the data like this
TempData.ErrorMessage = "some message";
var foo = TempDataKeys.ErrorMessage;
I know that it may look like the use of the ViewBag however, I need to have compile-time safety which ViewBag doesn't have. Anyway, the ViewBag works completely different from what I want to achieve.
Any ideas?
Cheers
You could go down the path of Extension methods
Looks quite neat:
public static class Extension
{
public const string _ERROR = "ErrorMessage";
public static void SetErrorMessage(this ITempDataDictionary #this, string message)
{
#this[_ERROR] = message;
}
public static string GetErrorMessage(this ITempDataDictionary #this) =>
#this[_ERROR]?.ToString();
}
And then you can just get it like this var message = TempData.GetErrorMessage(); and you can set it using TempData.SetErrorMessage("Some Error");
Similarly create Set/Update funcitons.
I think another way you could achieve this is extend the Controller/PageModel, where you will be implementing your own TempData because I don't think you can override it. This could make the base class messy and you will essentially including alot of functionality in every single controller where you will use this base class and might be sharing functionality that you don't need.
I am setting NullDisplayText in the DisplayFormat from resource through the following code
public class LocalizedDisplayFormatAttribute : DisplayFormatAttribute
{
private readonly PropertyInfo _propertyInfo;
public LocalizedDisplayFormatAttribute(string resourceKey, Type resourceType)
: base()
{
this._propertyInfo = resourceType.GetProperty(resourceKey, BindingFlags.Static | BindingFlags.Public);
if (this._propertyInfo == null)
{
return;
}
base.NullDisplayText = (string)this._propertyInfo.GetValue(this._propertyInfo.DeclaringType, null);
}
public new string NullDisplayText
{
get
{
return base.NullDisplayText;
}
set
{
base.NullDisplayText = value;
}
}
}
My default culture used is "en-US",Once I change the culture to es-AR and load the pages its working fine, but when I change the culture back to en-US fields are not getting converted back.
I change the culture throught the following way
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
try
{
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get("CurrentCulture");
string culutureCode = cookie != null && !string.IsNullOrEmpty(cookie.Value) ? cookie.Value : "en";
CultureInfo ci = new CultureInfo(culutureCode);
System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
System.Threading.Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(ci.Name);
}
catch
{
}
}
I use DisplayFormat attribute in ViewModel as
public class AlarmCodeDetailsViewModel
{
/// <summary>
/// Gets or sets the alarm code ID
/// </summary>
public int AlarmCodeID { get; set; }
/// <summary>
/// Gets or sets the alarm code
/// </summary>
[LocalizedDisplayName("Label_AlarmCode")]
[LocalizedDisplayFormatAttribute("Warning_NullDisplayText", typeof(Properties.Resources), HtmlEncode = false)]
public string Code { get; set; }
/// <summary>
/// Gets or sets the Description
/// </summary>
[LocalizedDisplayName("Label_Description")]
[LocalizedDisplayFormatAttribute("Warning_NullDisplayText", typeof(Properties.Resources), HtmlEncode = false)]
public string Description { get; set; }
/// <summary>
/// Gets or sets the Notes
/// </summary>
[LocalizedDisplayName("Label_Notes")]
[LocalizedDisplayFormatAttribute("Warning_NullDisplayText", typeof(Properties.Resources), HtmlEncode = false)]
public string Notes { get; set; }
}
Here's some insight into what's wrong.
Mvc is using a form of TypeDescriptor (AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type)) to read attributes off your models. The TypeDescriptors are caching information about properties and attributes. So your LocalizedDisplayFormatAttribute attribute is only getting instantiated once, in terms of the TypeDescriptor api's, which means that the resource information is only read once (at construction). See the bottom of the answer for references.
Solutions that dont work
The blink reaction is to just pull the latest resource information from your LocalizedDisplayFormatAttribute NullDisplayText every time it is accessed via the getter. Unfortunately DisplayFormatAttribute NullDisplayTextis not virtual, and you are shadowing the property with a new keyword. This won't work from a polymorphic dispatch perspective (Mvc is calling the getter as a DisplayFormatAttribute instead of a LocalizedDisplayFormatAttribute, so your shadowed property is never being called)
I tried TypeDescriptor.Refresh() overloads https://msdn.microsoft.com/en-us/library/z1ztz056(v=vs.110).aspx and had no luck
The remaining options that I'm aware of are not as convenient or not amazing in one way or another. Probably not recommended.
Some way to successfully refresh the AssociatedMetadataTypeTypeDescriptionProvider TypeDescriptors. I'm not too familiar with these, so there could totally be one. I'm just not seeing one currently.
Rework or create a ModelMetadataProvider of your own. Everything is open source, so its possible, though I'm not sure I would recommend it except as a last resort.
You could possibly work with the TypeDescriptor api's to force a re-instantiation of your attribute whenever it is being pulled. See https://stackoverflow.com/a/12143653/897291.
Model the needed properties directly in MVC (as model properties, instead of attributes). Could either be entirely new properties, or you could have some sort of logic within your original properties, that when null return something else. Awkward to deal with though.
Nothing great, I know. Maybe this will give someone else enough insight to come up with something better?
To verify this yourself, see
https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/DataAnnotationsModelMetadataProvider.cs CreateMetaData method which calls SetFromDataTypeAndDisplayAttributes method setting result.NullDisplayText = displayFormatAttribute.NullDisplayText;
DataAnnotationsModelMetadataProvider extends AssociatedMetadataProvider which is repsonsible for passing in the attributes. See https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/AssociatedMetadataProvider.cs GetMetadataForProperty method as an example.
I've got a WCF DataContract that looks like the following:
namespace MyCompanyName.Services.Wcf
{
[DataContract(Namespace = "http://mycompanyname/services/wcf")]
[Serializable]
public class DataContractBase
{
[DataMember]
public DateTime EditDate { get; set; }
// code omitted for brevity...
}
}
When I add a reference to this service in Visual Studio, this proxy code is generated:
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.3082")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://mycompanyname/services/wcf")]
public partial class DataContractBase : object, System.ComponentModel.INotifyPropertyChanged {
private System.DateTime editDateField;
private bool editDateFieldSpecified;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public System.DateTime EditDate {
get {
return this.editDateField;
}
set {
this.editDateField = value;
this.RaisePropertyChanged("EditDate");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool EditDateSpecified {
get {
return this.editDateFieldSpecified;
}
set {
this.editDateFieldSpecified = value;
this.RaisePropertyChanged("EditDateSpecified");
}
}
// code omitted for brevity...
}
As you can see, besides generating a backing property for EditDate, an additional <propertyname>Specified property is generated. All good, except that when I do the following:
DataContractBase myDataContract = new DataContractBase();
myDataContract.EditDate = DateTime.Now;
new MyServiceClient.Update(new UpdateRequest(myDataContract));
the EditDate was not getting picked up by the endpoint of the service (does not appear in the transmitted XML).
I debugged the code and found that, although I was setting EditDate, the EditDateSpecified property wasn't being set to true as I would expect; hence, the XML serializer was ignoring the value of EditDate, even though it's set to a valid value.
As a quick hack I modified the EditDate property to look like the following:
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public System.DateTime EditDate {
get {
return this.editDateField;
}
set {
this.editDateField = value;
// hackhackhack
if (value != default(System.DateTime))
{
this.EditDateSpecified = true;
}
// end hackhackhack
this.RaisePropertyChanged("EditDate");
}
}
Now the code works as expected, but of course every time I re-generate the proxy, my modifications are lost. I could change the calling code to the following:
DataContractBase myDataContract = new DataContractBase();
myDataContract.EditDate = DateTime.Now;
myDataContract.EditDateSpecified = true;
new MyServiceClient.Update(new UpdateRequest(myDataContract));
but that also seems like a hack-ish waste of time.
So finally, my question: does anyone have a suggestion on how to get past this unintuitive (and IMO broken) behavior of the Visual Studio service proxy generator, or am I simply missing something?
It might be a bit unintuitive (and caught me off guard and reeling, too!) - but it's the only proper way to handle elements that might or might not be specified in your XML schema.
And it also might seem counter-intuitive that you have to set the xyzSpecified flag yourself - but ultimately, this gives you more control, and WCF is all about the Four Tenets of SOA of being very explicit and clear about your intentions.
So basically - that's the way it is, get used to it :-) There's no way "past" this behavior - it's the way the WCF system was designed, and for good reason, too.
What you always can do is catch and handle the this.RaisePropertyChanged("EditDate"); event and set the EditDateSpecified flag in an event handler for that event.
try this
[DataMember(IsRequired=true)]
public DateTime EditDate { get; set; }
This should omit the EditDateSpecified property since the field is specified as required
Rather than change the setters of the autogenerated code, you can use an extension class to 'autospecify' (bind the change handler event). This could have two implementations -- a "lazy" one (Autospecify) using reflection to look for fieldSpecified based on the property name, rather than listing them all out for each class in some sort of switch statement like Autonotify:
Lazy
public static class PropertySpecifiedExtensions
{
private const string SPECIFIED_SUFFIX = "Specified";
/// <summary>
/// Bind the <see cref="INotifyPropertyChanged.PropertyChanged"/> handler to automatically set any xxxSpecified fields when a property is changed. "Lazy" via reflection.
/// </summary>
/// <param name="entity">the entity to bind the autospecify event to</param>
/// <param name="specifiedSuffix">optionally specify a suffix for the Specified property to set as true on changes</param>
/// <param name="specifiedPrefix">optionally specify a prefix for the Specified property to set as true on changes</param>
public static void Autospecify(this INotifyPropertyChanged entity, string specifiedSuffix = SPECIFIED_SUFFIX, string specifiedPrefix = null)
{
entity.PropertyChanged += (me, e) =>
{
foreach (var pi in me.GetType().GetProperties().Where(o => o.Name == specifiedPrefix + e.PropertyName + specifiedSuffix))
{
pi.SetValue(me, true, BindingFlags.SetField | BindingFlags.SetProperty, null, null, null);
}
};
}
/// <summary>
/// Create a new entity and <see cref="Autospecify"/> its properties when changed
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="specifiedSuffix"></param>
/// <param name="specifiedPrefix"></param>
/// <returns></returns>
public static T Create<T>(string specifiedSuffix = SPECIFIED_SUFFIX, string specifiedPrefix = null) where T : INotifyPropertyChanged, new()
{
var ret = new T();
ret.Autospecify(specifiedSuffix, specifiedPrefix);
return ret;
}
}
This simplifies writing convenience factory methods like:
public partial class MyRandomClass
{
/// <summary>
/// Create a new empty instance and <see cref="PropertySpecifiedExtensions.Autospecify"/> its properties when changed
/// </summary>
/// <returns></returns>
public static MyRandomClass Create()
{
return PropertySpecifiedExtensions.Create<MyRandomClass>();
}
}
A downside (other than reflection, meh) is that you have to use the factory method to instantiate your classes or use .Autospecify before (?) you make any changes to properties with specifiers.
No Reflection
If you don't like reflection, you could define another extension class + interface:
public static class PropertySpecifiedExtensions2
{
/// <summary>
/// Bind the <see cref="INotifyPropertyChanged.PropertyChanged"/> handler to automatically call each class's <see cref="IAutoNotifyPropertyChanged.Autonotify"/> method on the property name.
/// </summary>
/// <param name="entity">the entity to bind the autospecify event to</param>
public static void Autonotify(this IAutoNotifyPropertyChanged entity)
{
entity.PropertyChanged += (me, e) => ((IAutoNotifyPropertyChanged)me).WhenPropertyChanges(e.PropertyName);
}
/// <summary>
/// Create a new entity and <see cref="Autonotify"/> it's properties when changed
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T Create<T>() where T : IAutoNotifyPropertyChanged, new()
{
var ret = new T();
ret.Autonotify();
return ret;
}
}
/// <summary>
/// Used by <see cref="PropertySpecifiedExtensions.Autonotify"/> to standardize implementation behavior
/// </summary>
public interface IAutoNotifyPropertyChanged : INotifyPropertyChanged
{
void WhenPropertyChanges(string propertyName);
}
And then each class themselves defines the behavior:
public partial class MyRandomClass: IAutoNotifyPropertyChanged
{
public void WhenPropertyChanges(string propertyName)
{
switch (propertyName)
{
case "field1": this.field1Specified = true; return;
// etc
}
}
}
The downside to this is, of course, magic strings for property names making refactoring difficult, which you could get around with Expression parsing?
Further information
On the MSDN here
In her answer, Shreesha explains that:
"Specified" fields are only generated on optional parameters that are structs. (int, datetime, decimal etc). All such variables will have additional variable generated with the name Specified.
This is a way of knowing if a parameter is really passed between the client and the server.
To elaborate, an optional integer, if not passed, would still have the dafault value of 0. How do you differentiate between this and the one that was actually passed with a value 0 ? The "specified" field lets you know if the optional integer is passed or not. If the "specified" field is false, the value is not passed across. If it true, the integer is passed.
so essentially, the only way to have these fields set is to set them manually, and if they aren't set to true for a field that has been set, then that field will be missed out in the SOAP message of the web-service call.
What I did in the end was build a method to loop through all the members of the object, and if the property has been set, and if there is a property called name _Specified then set that to true.
Ian,
Please ignore my previous answers, was explaining how to suck eggs. I've voted to delete them.
Could you tell me which version of Visual Studio you're using, please?
In VS2005 client - in the generated code, I get the <property>Specified flags, but no event raised on change of values. To pass data I have to set the <property>Specified flag.
In Visual Web Developer 2008 Express client - in the generated code, I get no <property>Specified flags, but I do get the event on change of value.
Seems to me that this functionality has evolved and the Web Dev 2008 is closer to what you're after and is more intuitive, in that you don't need to set flags once you've set a value.
Bowthy
Here's a simple project that can modify the setters in generated WCF code for optional properties to automatically set the *Specified flags to true when setting the related value.
https://github.com/b9chris/WcfClean
Obviously there are situations where you want manual control over the *Specified flag so I'm not recommending it to everyone, but in most simple use cases the *Specified flags are just an extra nuisance and automatically setting them saves time, and is often more intuitive.
Note that Mustafa Magdy's comment on another answer here will solve this for you IF you control the Web Service publication point. However, I usually don't control the Web Service publication and am just consuming one, and have to cope with the *Specified flags in some simple software where I'd like this automated. Thus this tool.
Change proxy class properties to nullable type
ex :
bool? confirmed
DateTime? checkDate