Execute request model binding manually - c#

In Asp.Net you can automatically parse request data into an input model / API contract using for example the attributes FromBody, FromQuery, and FromRoute. I want to execute this behavior myself. Let me explain.
I want to have a custom policy requirement based on a combination of data passed to the requirement and the target entity which is passed inside the request data. But this target entity id can be in different locations. Usually the body, but for example the route or the query when using HttpGet. So I thought about putting this information about the location above the controller endpoint using an attribute. The following pseudo-code is based on the guess that I need the BindingSource.
I would create API contracts using an interface defining the location of the target id.
public interface ITargetEntityContract {
public string TargetEntityId { get; set; }
}
public class ExampleRequest : ITargetEntityContract {
public string TargetEntityId { get; set; } = default!;
public string SomeOtherData { get; set; } = default!;
}
Then I would create an attribute to define the location:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class TargetEntityLocationAttribute : Attribute {
public Type ContractType { get; }
public BindingSource BindingSource { get; }
public TargetEntityLocationAttribute(Type contractType, BindingSource bindingSource) {
if (!typeof(ITargetEntityContract).IsAssignableFrom(contractType))
throw new Exception("Contract has to implement the interface ITargetEntityContract");
this.ContractType = contractType;
this.BindingSource = bindingSource;
}
}
And you would apply this onto a controller endpoint the following way:
[TargetEntityLocation(typeof(ExampleRequest), BindingSource.Body)]
public async Task<IActionResult> SomeEndpointAsync([FromBody] ExampleRequest requestData) {
}
Within the IAuthorizationHandler I would use these classes the following way:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ExampleAuthorizationRequirement requirement) {
var endpoint = this._httpContextAccessor.HttpContext!.GetEndpoint();
if (endpoint is null)
throw new Exception("Some error");
var targetEntityLocation = endpoint.MetaData.GetMetaData<TargetEntityLocationAttribute>();
if (targetEntityLocation is null)
throw new Exception("some error");
var targetEntityModel = SomeAlmightyParser.ParseFromSource(this._httpContextAccessor.HttpContext!, targetEntityLocation.ContractType, targetEntityLocation.BindingSource) as ITargetEntityContract;
// do something with targetEntityModel.TargetEntityId
}
Is there a way to parse the request data of the HttpContext into the given model based on the data location?

If the source of the data is RequestBody,you could read the stream with StreamReader and deserialize the string you get;
If the source of the data is RequestQuery,you could map the key-value pairs to your target object with reflection
For example,I tried as below:
//read obj from requestbody
using var reader = new StreamReader(context.Request.Body);
var bodystr = reader.ReadToEndAsync().Result;
if (bodystr != "")
{
var obj1 = JsonSerializer.Deserialize(bodystr, typeof(ExampleRequest), new JsonSerializerOptions() { });
}
//read simple obj from query
var querycollection = context.Request.Query;
var type = typeof(ExampleRequest);
var properties = type.GetProperties();
object obj = Activator.CreateInstance(type);
foreach (var propinfo in properties)
{
var propname = propinfo.Name;
if (querycollection.ContainsKey(propname))
{
var proptype = propinfo.PropertyType;
propinfo.SetValue(obj, Convert.ChangeType(querycollection[propname].ToString(), proptype),null);
}
}
var obj2 = obj as ExampleRequest;
Result:

Related

Validate nested Json object in custom model binder in .net core

I created a custom model binder in my .NET Core 3.1 API to validate all JSON parameters (by the help of this post How to validate json request body as valid json in asp.net core).Due to security concern I am checking all the incoming request parameter with the actual model properties(entity model) to avoid parameter tempering(adding additional properties while sending the API request). This is working fine for normal json request but if I have a nested json(nested entity model)then it is validating only main properties(inside properties are not validating)
Example:
for non nested entity models the code is working fine, but if it is a nested models like bellow
public class dep
{
public int depId { get; set; }
public string depName { get; set; }
}
public class EmpModel
{
public dep DepDetails { get; set; }
public string empName { get; set; }
}
The code will validate only the main properties like DepDetails, empName but it wont check the inside properties of DepDetails
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
var modelName = bindingContext.BinderModelName ?? "XJson";
var modelType = bindingContext.ModelType;
// create a JsonTextReader
var req = bindingContext.HttpContext.Request;
var raw = req.Body;
if (raw == null)
{
bindingContext.ModelState.AddModelError(modelName, "invalid request body stream");
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
JsonTextReader reader = new JsonTextReader(new StreamReader(raw));
try
{
var jsonT = (JObject)JToken.Load(reader, this._loadSettings);
JObject reqeJson = JObject.Parse(jsonT.ToString());
var RequestParameters = reqeJson.Properties().ToList();
var actualParam = bindingContext.ModelMetadata.Properties.ToList();
//checking for additional parameters in input
//Here I am checking if any additional property is appended with the data and for the
//above model in both actualParam and RequestParameters I will get only 2 instead of 3
bool flag = true;
foreach (var obj in RequestParameters)
{
if(!actualParam.Any(item => item.Name.ToUpper() == obj.Name.ToUpper())){
flag = false;
break;
}
JObject tmp = JObject.Parse(obj.ToString());
};
if (!flag)
{
bindingContext.ModelState.AddModelError(modelName, "Additional Data is present in Request");
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
var o = jsonT.ToObject(modelType);
bindingContext.Result = ModelBindingResult.Success(o);
}
catch (Exception e)
{
bindingContext.ModelState.AddModelError(modelName, e.ToString());
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
The Real problem is the content of RequestParameters and actualParam is {DepDetails,empName} but I should get as {depId,depName,empName}
If this method is not correct then please help me with some alternatives.

Upload a file and also pass model data

I need to import a file, and also the Person model that I have shown below.
I am able to upload the file, However, I am not able to retrieve the Person model data in the importFileAndOtherInfo method that I have written.
Note: I am testing this web API via Postman. How can I upload a file, and also send Person Model data via Postman?
Person
int pId
string PName
School schoolAttended
My implementation:
[HttpPost]
public async Task<int> importFileAndOtherInfo(Person person)
{
var stream = HttpContext.Current.Request.Files[0].InputStream
// HOW TO RETRIEVE THE PERSON DATA HERE.
}
What I understood from your question is, you want to pass model data and the file in stream at the same time; you can't send it directly, the way around is to send file with IFormFile and add create your own model binder as follows,
public class JsonWithFilesFormDataModelBinder: IModelBinder
{
private readonly IOptions<MvcJsonOptions> _jsonOptions;
private readonly FormFileModelBinder _formFileModelBinder;
public JsonWithFilesFormDataModelBinder(IOptions<MvcJsonOptions> jsonOptions, ILoggerFactory loggerFactory)
{
_jsonOptions = jsonOptions;
_formFileModelBinder = new FormFileModelBinder(loggerFactory);
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
// Retrieve the form part containing the JSON
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);
if (valueResult == ValueProviderResult.None)
{
// The JSON was not found
var message = bindingContext.ModelMetadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName);
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message);
return;
}
var rawValue = valueResult.FirstValue;
// Deserialize the JSON
var model = JsonConvert.DeserializeObject(rawValue, bindingContext.ModelType, _jsonOptions.Value.SerializerSettings);
// Now, bind each of the IFormFile properties from the other form parts
foreach (var property in bindingContext.ModelMetadata.Properties)
{
if (property.ModelType != typeof(IFormFile))
continue;
var fieldName = property.BinderModelName ?? property.PropertyName;
var modelName = fieldName;
var propertyModel = property.PropertyGetter(bindingContext.Model);
ModelBindingResult propertyResult;
using (bindingContext.EnterNestedScope(property, fieldName, modelName, propertyModel))
{
await _formFileModelBinder.BindModelAsync(bindingContext);
propertyResult = bindingContext.Result;
}
if (propertyResult.IsModelSet)
{
// The IFormFile was successfully bound, assign it to the corresponding property of the model
property.PropertySetter(model, propertyResult.Model);
}
else if (property.IsBindingRequired)
{
var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);
bindingContext.ModelState.TryAddModelError(modelName, message);
}
}
// Set the successfully constructed model as the result of the model binding
bindingContext.Result = ModelBindingResult.Success(model);
}
}
Model
[ModelBinder(typeof(JsonWithFilesFormDataModelBinder), Name = "data")]
public class Person
{
public int pId {get; set;}
public string PName {get; set;}
public School schoolAttended {get; set;}
public IFormFile File { get; set; }
}
Postman request:
I am using the same in netcoreapp2.2. Works successfully.
Now when migrating from notecoreapp2.2 to netcoreapp3.1 I'm facing issues with private readonly IOptions<MvcJsonOptions> _jsonOptions;
As MvcJsonOptions is a breaking change from core 2.2 to 3.0.
Check this:
Migration of netcoreapp2.2 to netcoreapp3.1 - convert MvcJsonOptions to core3.1 compatable

Using [JsonProperty("name")] in ModelState.Errors

We have a couple of models that override the name via JsonProperty, but this causes an issue when we get validation errors through ModelState. For example:
class MyModel
{
[JsonProperty("id")]
[Required]
public string MyModelId {get;set;}
}
class MyModelController
{
public IHttpActionResult Post([FromBody] MyModel model)
{
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
/* etc... */
}
}
The above Post will return the error The MyModelId field is required. which isn't accurate. We'd like this to say The id field is required.. We've attempted using [DataMember(Name="id")] but get the same result.
Question 1: Is there a way we can get ModelState errors to show the JSON property name rather than the C# property name aside from providing our own error messages on every [Required] attribute?
-- Update --
I've been playing around with this and found a "do-it-yourself" method for re-creating the error messages using custom property names. I'm really hoping there's a built-in way to do this, but this seems to do the job...
https://gist.github.com/Blackbaud-JasonTremper/b64dc6ddb460afa1698daa6d075857e4
Question 2: Can ModelState.Key be assumed to match the <parameterName>.<reflectedProperty> syntax or are there cases where this might not be true?
Question 3: Is there an easier way to determine what the JSON parameter name is expected to be rather than searching via reflection on [DataMember] or [JsonProperty] attributes?
Did you try using DisplayName attribute?
displayname attribute vs display attribute
Also, you can assign an error message to [Required] attribute.
[Required(ErrorMessage = "Name is required")]
I also faced this problem, I modified some code from your link to fit my WebAPI. modelState will also store the old key which is the variable name of the model, plus the Json Property names.
First, create the filter ValidateModelStateFilter
Add [ValidateModelStateFilter] above controller method
The filter source code:
public class ValidateModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var descriptor = actionContext.ActionDescriptor;
var modelState = actionContext.ModelState;
if (descriptor != null)
{
var parameters = descriptor.GetParameters();
var subParameterIssues = modelState.Keys
.Where(s => s.Contains("."))
.Where(s => modelState[s].Errors.Any())
.GroupBy(s => s.Substring(0, s.IndexOf('.')))
.ToDictionary(g => g.Key, g => g.ToArray());
foreach (var parameter in parameters)
{
var argument = actionContext.ActionArguments[parameter.ParameterName];
if (subParameterIssues.ContainsKey(parameter.ParameterName))
{
var subProperties = subParameterIssues[parameter.ParameterName];
foreach (var subProperty in subProperties)
{
var propName = subProperty.Substring(subProperty.IndexOf('.') + 1);
var property = parameter.ParameterType.GetProperty(propName);
var validationAttributes = property.GetCustomAttributes(typeof(ValidationAttribute), true);
var value = property.GetValue(argument);
modelState[subProperty].Errors.Clear();
foreach (var validationAttribute in validationAttributes)
{
var attr = (ValidationAttribute)validationAttribute;
if (!attr.IsValid(value))
{
var parameterName = GetParameterName(property);
// modelState.AddModelError(subProperty, attr.FormatErrorMessage(parameterName));
modelState.AddModelError(parameterName, attr.FormatErrorMessage(parameterName));
}
}
}
}
}
}
}
private string GetParameterName(PropertyInfo property)
{
var dataMemberAttribute = property.GetCustomAttributes<DataMemberAttribute>().FirstOrDefault();
if (dataMemberAttribute?.Name != null)
{
return dataMemberAttribute.Name;
}
var jsonProperty = property.GetCustomAttributes<JsonPropertyAttribute>().FirstOrDefault();
if (jsonProperty?.PropertyName != null)
{
return jsonProperty.PropertyName;
}
return property.Name;
}
}
You can access the parameter type, get the json name, and then replace the property name with the json name. Something like this:
var invalidParameters = (from m in actionContext.ModelState
where m.Value.Errors.Count > 0
select new InvalidParameter
{
ParameterName = m.Key,
ConstraintViolations = (from msg in m.Value.Errors select msg.ErrorMessage).ToArray()
}).AsEnumerable().ToArray();
if (actionContext.ActionDescriptor.Parameters.Count == 1)
{
var nameMapper = new Dictionary<string, string>();
foreach (var property in actionContext.ActionDescriptor.Parameters[0].ParameterType.GetProperties())
{
object[] attributes = property.GetCustomAttributes(typeof(JsonPropertyNameAttribute), false);
if (attributes.Length != 1) continue;
nameMapper.Add(property.Name, ((JsonPropertyNameAttribute) attributes[0]).Name);
}
var modifiedInvalidParameters = new List<InvalidParameter>();
foreach (var invalidParameter in invalidParameters)
{
if(invalidParameter.ParameterName != null && nameMapper.TryGetValue(invalidParameter.ParameterName, out var mappedName))
{
var modifiedConstraintViolations = new List<string>();
foreach (var constraintViolation in invalidParameter.ConstraintViolations ?? Enumerable.Empty<string>())
{
modifiedConstraintViolations.Add(constraintViolation.Replace(invalidParameter.ParameterName, mappedName));
}
modifiedInvalidParameters.Add(new InvalidParameter
{
ParameterName = mappedName,
ConstraintViolations = modifiedConstraintViolations.ToArray()
});
}
else
{
modifiedInvalidParameters.Add(invalidParameter);
}
}
invalidParameters = modifiedInvalidParameters.ToArray();
}
public struct InvalidParameter
{
[JsonPropertyName("parameter_name")]
public string? ParameterName { get; set; }
[JsonPropertyName("constraint_violations")]
public string[]? ConstraintViolations { get; set; }
}

Adding a custom query backed Navigation Property to ODataConventionModelBuilder

Situation
I created the following Model classes
public class Car
{
public int Id {get;set;}
public string Name {get;set;}
public virtual ICollection<PartState> PartStates {get;set; }
}
public class PartState
{
public int Id {get;set;}
public string State {get;set;}
public int CarId {get;set;}
public virtual Car Car {get;set;}
public int PartId {get;set;}
public virtual Part Part {get;set;}
}
public class Part
{
public int Id {get;set;}
public string Name {get;set;}
}
And a matching DbContext
public class CarContext : DbContext
{
public DbSet<Car> Cars {get;set;}
public DbSet<PartState> PartStates {get;set;}
public DbSet<Part> Parts {get;set;}
}
And created a WebApplication to make this available via odata, using the scaffolding template "Web API 2 OData Controller with Actions, using Entity Framework"
also i create following webapi config:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Car>("Cars");
builder.EntitySet<PartState>("PartStates");
builder.EntitySet<Part>("Parts");
var edmModel = builder.GetEdmModel();
config.Routes.MapODataRoute("odata", "odata", edmModel);
}
}
I now want to add the following Method to my Cars Controller
// GET: odata/Cars(5)/Parts
[Queryable]
public IQueryable<Part> GetParts([FromODataUri] int key)
{
var parts = db.PartStates.Where(s => s.CarId == key).Select(s => s.Part).Distinct();
return parts;
}
And retrieve the data with this Url:
http://localhost/odata/Cars(1)/Parts
But it does not work, instead i get the following error:
{
"odata.error":{
"code":"","message":{
"lang":"en-US","value":"No HTTP resource was found that matches the request URI 'http://localhost/odata/Cars(1)/Parts'."
},"innererror":{
"message":"No routing convention was found to select an action for the OData path with template '~/entityset/key/unresolved'.","type":"","stacktrace":""
}
}
}
Question
So my question is, is that even possible?!
I tried to create a Navigation property manually, and added it to the edm model, while this does resolve the issue to invoke the new method, it also introduces new Errors.
EDIT:
What id did try to add it manually in this way:
var edmModel = (EdmModel)builder.GetEdmModel();
var carType = (EdmEntityType)edmModel.FindDeclaredType("Car");
var partType = (EdmEntityType)edmModel.FindDeclaredType("Part");
var partsProperty = new EdmNavigationPropertyInfo();
partsProperty.TargetMultiplicity = EdmMultiplicity.Many;
partsProperty.Target = partType;
partsProperty.ContainsTarget = false;
partsProperty.OnDelete = EdmOnDeleteAction.None;
partsProperty.Name = "Parts";
var carsProperty = new EdmNavigationPropertyInfo();
carsProperty.TargetMultiplicity = EdmMultiplicity.Many;
carsProperty.Target = carType;
carsProperty.ContainsTarget = false;
carsProperty.OnDelete = EdmOnDeleteAction.None;
carsProperty.Name = "Cars";
var nav = EdmNavigationProperty.CreateNavigationPropertyWithPartner(partsProperty, carsProperty);
carType.AddProperty(nav);
config.Routes.MapODataRoute("odata", "odata", edmModel);
while this allowed me to invoke the above speciefied method trough the also above specified URL, it gave me the following error:
{
"odata.error":{
"code":"","message":{
"lang":"en-US","value":"An error has occurred."
},"innererror":{
"message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata=fullmetadata; charset=utf-8'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
"message":"The related entity set could not be found from the OData path. The related entity set is required to serialize the payload.","type":"System.Runtime.Serialization.SerializationException","stacktrace":" at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)\r\n at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)\r\n at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()"
}
}
}
}
You have to call "AddNavigationTarget" on the EntitySet.
Assume that your namespace is "MyNamespace", then add the following code to your WebApiConfig.cs. In this way, retrieving the data with "Get: odata/Cars(1)/Parts" will work.
var cars = (EdmEntitySet)edmModel.EntityContainers().Single().FindEntitySet("Cars");
var parts = (EdmEntitySet)edmModel.EntityContainers().Single().FindEntitySet("Parts");
var carType = (EdmEntityType)edmModel.FindDeclaredType("MyNamespace.Car");
var partType = (EdmEntityType)edmModel.FindDeclaredType("MyNamespace.Part");
var partsProperty = new EdmNavigationPropertyInfo();
partsProperty.TargetMultiplicity = EdmMultiplicity.Many;
partsProperty.Target = partType;
partsProperty.ContainsTarget = false;
partsProperty.OnDelete = EdmOnDeleteAction.None;
partsProperty.Name = "Parts";
cars.AddNavigationTarget(carType.AddUnidirectionalNavigation(partsProperty), parts);
Taking #FengZhao's answer further, in order to get the url odata/Cars working you also need to register the navigation property link builder to entity set link builder.
var cars = (EdmEntitySet)edmModel.EntityContainers().Single().FindEntitySet("Cars");
var parts = (EdmEntitySet)edmModel.EntityContainers().Single().FindEntitySet("Parts");
var carType = (EdmEntityType)edmModel.FindDeclaredType("MyNamespace.Car");
var partType = (EdmEntityType)edmModel.FindDeclaredType("MyNamespace.Part");
var partsProperty = new EdmNavigationPropertyInfo();
partsProperty.TargetMultiplicity = EdmMultiplicity.Many;
partsProperty.Target = partType;
partsProperty.ContainsTarget = false;
partsProperty.OnDelete = EdmOnDeleteAction.None;
partsProperty.Name = "Parts";
var navigationProperty = carType.AddUnidirectionalNavigation(partsProperty);
cars.AddNavigationTarget(navigationProperty, parts);
var linkBuilder = edmModel.GetEntitySetLinkBuilder(cars);
linkBuilder.AddNavigationPropertyLinkBuilder(navigationProperty,
new NavigationLinkBuilder((context, property) =>
context.GenerateNavigationPropertyLink(property, false), true));
Web Api cannot resolve your URL against any of registered URI templates.
Use Route Debugger to figure this out.
http://blogs.msdn.com/b/webdev/archive/2013/04/04/debugging-asp-net-web-api-with-route-debugger.aspx
I believe our problem is the id parameter that you pass via url
Try making it more explicit
config.Routes.MapHttpRoute
(
"DefaultInternalApi",
"api/{controller}/{objectType}/{Id}/{relation}",
defaults:
new
{
Id = System.Web.Http.RouteParameter.Optional,
}
);
The original question could also have solved their issue by adding Parts as a property on the Car object if it made sense for them. That is, adding the navigation property for real rather than persuading the OData model builder to register it. For example:
public class Car
{
public int Id {get;set;}
public string Name {get;set;}
public virtual ICollection<PartState> PartStates {get;set; }
public virtual ICollection<Part> Parts { get => this.PartStates?.Select(partState => partState.Part)
}

Is it possible to modify the attribute of a property at runtime?

Is it possible to modify the attribute of a property at runtime?
let's say I have some class:
public class TheClass
{
[TheAttribute]
public int TheProperty { get; set; }
}
Is there a way to do this?
if (someCondition)
{
// disable attribute. Is this possible and how can this be done?
}
No this is not possible. You cannot modify attribute values from metadata, or metadata in general, at runtime
Strictly speaking the above is not true. There are certain APIs which do allow allow for some metadata generation and modification. But they are very scenario specific, (ENC, profiling, debugging) and should not be used in general purpose programs.
It depends; from a reflection perspective: no. You can't. But if you are talking about attributes used by System.ComponentModel in things like data-binding, they you can use TypeDescriptor.AddAttributes to append extra attributes. Or other customer models involving custom descriptors. So it depends on the use-case.
In the case of xml serialization, it gets more interesting. Firstly, we can use fun object models:
using System;
using System.Xml.Serialization;
public class MyData
{
[XmlAttribute]
public int Id { get; set; }
[XmlAttribute]
public string Name { get; set; }
[XmlIgnore]
public bool NameSpecified { get; set; }
static void Main()
{
var ser = new XmlSerializer(typeof(MyData));
var obj1 = new MyData { Id = 1, Name = "Fred", NameSpecified = true };
ser.Serialize(Console.Out, obj1);
Console.WriteLine();
Console.WriteLine();
var obj2 = new MyData { Id = 2, Name = "Fred", NameSpecified = false };
ser.Serialize(Console.Out, obj2);
}
}
The bool {name}Specified {get;set;} pattern (along with bool ShouldSerialize{name}()) is recognised and used to control which elements to include.
Another alternative is to use the non-default ctor:
using System;
using System.Xml.Serialization;
public class MyData
{
[XmlAttribute]
public int Id { get; set; }
public string Name { get; set; }
static void Main()
{
var obj = new MyData { Id = 1, Name = "Fred" };
XmlAttributeOverrides config1 = new XmlAttributeOverrides();
config1.Add(typeof(MyData),"Name",
new XmlAttributes { XmlIgnore = true});
var ser1 = new XmlSerializer(typeof(MyData),config1);
ser1.Serialize(Console.Out, obj);
Console.WriteLine();
Console.WriteLine();
XmlAttributeOverrides config2 = new XmlAttributeOverrides();
config2.Add(typeof(MyData), "Name",
new XmlAttributes { XmlIgnore = false });
var ser2 = new XmlSerializer(typeof(MyData), config2);
ser2.Serialize(Console.Out, obj);
}
}
Note though that if you use this second approach you need to cache the serializer instance, as it emits an assembly every time you do this. I find the first approach simpler...
Attributes are baked into code at compilation time. The only way you can define new attributes at run time is to generate new code at runtime (using Reflection.Emit, for example). But you cannot change the attributes of existing code.
You can put Boolean variable in the class to disable/enable the property instead of disabling it at run time.
You might want to look at this http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/5b0d356d-d006-43ff-bfcd-aa90dd8de6db
And Dave Morton's explanation on this blog http://blog.codinglight.com/2008/10/changing-attribute-parameters-at.html
Sounds like you want to consider implementing IXmlSerializable
You can implement IDataErrorInfo, then check range in Validate method.
public string this[string property] {
get { return Validate(property); }
}
public string Error { get; }
protected virtual string Validate(string property) {
var propertyInfo = this.GetType().GetProperty(property);
var results = new List<ValidationResult>();
var result = Validator.TryValidateProperty(
propertyInfo.GetValue(this, null),
new ValidationContext(this, null, null) {
MemberName = property
},
results);
if (!result) {
var validationResult = results.First();
return validationResult.ErrorMessage;
}
return string.Empty;
}
In sub class
protected override string Validate(string property) {
Debug.WriteLine(property);
if (property == nameof(YourProperty)) {
if (_property > 5) {
return "_property out of range";
}
}
return base.Validate(property);
}

Categories

Resources