Generic types, Interfaces - c#

I would like to create a generic notification engine. The idea is to have a single core engine to process any type of notification. This engine will process notification and handle all logging, error handling etc..
I created 3 simple interfaces:
public interface INotificationInput
{
/// <summary>
/// Friendly Name for logging/tracing usage
/// </summary>
string FriendlyName { get; set; }
string NotificationCode{ get; set; }
Double Version { get; set; }
}
public interface INotificationOutput
{
/// <summary>
/// Friendly Name for logging/tracing usage
/// </summary>
string FriendlyName { get; }
}
public interface INotificationProvider<out Toutput, Tinput> where Toutput : INotificationOutput where Tinput : INotificationInput
{
/// <summary>
/// Friendly Name for logging/tracing usage
/// </summary>
string FriendlyName { get; set; }
/// <summary>
/// Generates and returns an INotificationOutput from data
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
Toutput GenerateNotificationOutput(Tinput data);
}
So the INotificationProvider will chunk the INotificationInput to create a INotificationOutput.
That could be information to send a email, a sms, you name it, the engine will call the methods and do the magic of scheduling, logging, handling errors and so on..
I implemented the interface like this:
/// <summary>
/// INotificationInput represented by a dummy object
/// </summary>
public class DummyNotificationInput : INotificationInput
{
public string FriendlyName { get; set; }
public string NotificationCode { get; set; }
public double Version { get; set; }
}
public class DummyNotificationOutput : INotificationOutput
{
public string FriendlyName { get; private set; }
}
public class DummyProvider : INotificationProvider<DummyNotificationOutput, DummyNotificationInput>
{
public string FriendlyName { get; set; }
public DummyNotificationOutput GenerateNotificationOutput(DummyNotificationInput data)
{
throw new NotImplementedException();
}
}
Now I would like my engine to have a list of provider:
var providersList = new List<INotificationProvider<INotificationOutput, INotificationInput>>();
The problem is that I cannot to the following:
providersList.Add(new DummyProvider<DummyNotificationOutput, DummyNotificationInput>());
There must be a solution. Am I using the wrong approach?

The second generic type argument to INotificationProvider isn't covariant (at a conceptual level), but you're trying to use it as if it were. It is actually contravariant.
In your list of INotificationProvider objects you've defined the input notification as an INotificationInput. This means objects added to this list need to be able to accept any type of INotificationInput as input to their GenerateNotificationOutput function. You're trying to add an object that only knows how to handle DummyNotificationInput objects. It would fail if it were passed some other type of input.
Either your provider needs to accept INotificationInput objects, if you want to be able to add it to that list, or the list needs to define all of the objects as accepting DummyNotificationInput.

As Servy has already answered, you can't really do this due to what you providersList is expecting
With this in mind, it may actually be simpler to just make INotificationProvider non-generic:
public interface INotificationProvider
{
/// <summary>
/// Friendly Name for logging/tracing usage
/// </summary>
string FriendlyName { get; set; }
/// <summary>
/// Generates and returns an INotificationOutput from data
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
INotificationOutput GenerateNotificationOutput(INotificationInput data);
}
Then the DummyProvider becomes:
public class DummyProvider : INotificationProvider
{
public string FriendlyName { get; set; }
public INotificationOutput GenerateNotificationOutput(INotificationInput data)
{
throw new NotImplementedException();
}
}
Now, probably not what you had in mind - you are expecting to pass DummyNotificationInput instances to DummyProvider
You could just type check in your Provider code
public class DummyProvider : INotificationProvider
{
public string FriendlyName { get; set; }
public INotificationOutput GenerateNotificationOutput(INotificationInput data)
{
if (!(data is DummyNotificationInput)) throw new ArgumentException("Invalid type specified", "data");
return something...;
}
}
Obviously, you lose design time checking - but if you really need to put them in a covariant list you can't provide an implementor that has a derived generic type argument

Related

C# Complex object containing dictionary<string,bool> XML deserialization issue

XML to be deserialized:
<CheckOnlineStatus xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Lifecare.LIS.Integration.Hyland.Models">
<Database>string</Database>
<InstancesStillNearline>0</InstancesStillNearline>
<SeriesLocationMap xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:KeyValueOfstringboolean>
<d2p1:Key>test</d2p1:Key>
<d2p1:Value>true</d2p1:Value>
</d2p1:KeyValueOfstringboolean>
<d2p1:KeyValueOfstringboolean>
<d2p1:Key>test1</d2p1:Key>
<d2p1:Value>false</d2p1:Value>
</d2p1:KeyValueOfstringboolean>
</SeriesLocationMap>
<Status>string</Status>
<StudyInstanceUID>string</StudyInstanceUID>
<TotalClips>0</TotalClips>
<TotalExecutionTime>0</TotalExecutionTime>
<TotalInstances>0</TotalInstances>
</CheckOnlineStatus>
Data Models used:
using System.Collections.Generic;
using System.Xml.Serialization;
[XmlRoot("CheckOnlineStatus")]
/// <summary>
/// The CheckOnlineStatus entity.
/// </summary>
public class CheckOnlineStatus
{
[XmlElement("Status")]
/// <summary>
/// Gets or sets the Status
/// </summary>
public string Status { get; set; }
[XmlElement("Database")]
/// <summary>
/// Gets or sets the Database
/// </summary>
public string Database { get; set; }
/// <summary>
/// Gets or sets the StudyInstanceUID
/// </summary>
[XmlElement("StudyInstanceUID")]
public string StudyInstanceUID { get; set; }
/// <summary>
/// Gets or sets the TotalExecutionTime
/// </summary>
[XmlElement("TotalExecutionTime")]
public long TotalExecutionTime { get; set; }
/// <summary>
/// Gets or sets the TotalInstances
/// </summary>
[XmlElement("TotalInstances")]
public int TotalInstances { get; set; }
/// <summary>
/// Gets or sets the TotalClips
/// </summary>
[XmlElement("TotalClips")]
public int TotalClips { get; set; }
/// <summary>
/// Gets or sets the InstancesStillNearline
/// </summary>
[XmlElement("InstancesStillNearline")]
public int InstancesStillNearline { get; set; }
/// <summary>
/// Gets or sets the dictionary of series location map
/// </summary>
[XmlElement("SeriesLocationMap")]
public Dictionary<string, bool> SeriesLocationMap { get; set; }
}
Deserialization code, here result is above xml in string format:
(CheckOnlineStatus)new XmlSerializer(typeof(CheckOnlineStatus)).Deserialize(new MemoryStream(Encoding.UTF8.GetBytes(result)));
I am getting below runtime error:
Cannot serialize member
Lifecare.LIS.Integration.NDP.Models.CheckOnlineStatus.SeriesLocationMap
of type System.Collections.Generic.Dictionary`2[[System.String,
mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]],
because it implements IDictionary.
I tried couple of other things but it did not worked out e.g. https://www.codeproject.com/Questions/716556/XML-deserialize-Attributes-to-dictionary
Can someone please help here. Thanks.
As the exception (InvalidOperationException with an inner UnsupportedException) says you can't simply deserialize it into a Dictionary.
Here is one way to overcome of this difficulty:
Define KeyValue
[Serializable]
public struct KeyValue
{
public string Key { get; set; }
public bool Value { get; set; }
}
As an alternative you might take advantage of the built-in KeyValuePair<TKey, TValue>
Use XmlArray and XmlArrayItem
[XmlArray("SeriesLocationMap")]
[XmlArrayItem("KeyValueOfstringboolean")]
public List<KeyValue> IntermediateContainer { get; set; }
Use XmlIgnore to convert List to Dictionary<string, bool>
[XmlIgnore]
public Dictionary<string, bool> SeriesLocationMap => IntermediateContainer.ToDictionary(x => x.Key, y => y.Value);
I've tested it without namespaces, so you need to specify them at the right place to be able to read them properly.
Alternatively you might give it a try and use DataContractSerializer which does support Dictionary.

How to show responses example in swagger documenation

I developed asp.net web API and I used swagger to API documentation and consume purposes. I need to show swagger response model sample in swagger documentation as follows
This image I got from the internet
How can I add a response example as above image
My controller as follows
/// <param name="sDate">Start date</param>
/// <param name="eDate">End date</param>
/// <param name="lCode">Location code</param>
/// <param name="page">Page number</param>
/// <param name="pageSize">Page size</param>
[Route("lobbydetail")]
[SwaggerResponse(HttpStatusCode.OK, Type = typeof(ResultOutput<List<LDetailRecord>>))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(APIError))]
[SwaggerResponse(HttpStatusCode.InternalServerError, Type = typeof(APIError))]
public IHttpActionResult GetDetails(DateTime sDate, DateTime eDate, string lCode = null, int page = 1, int pageSize = 100)
{
try
{
if (sDate > eDate)
{
return Content(HttpStatusCode.BadRequest, new APIError("400", "Start date is greater than end date."));
}
var tID = Convert.ToInt32(jwtData.GetTokenClaim(TENANT_ID));
return Ok(dataView.GetDetailViewData(tID, sDate, eDate, lCode, page, pageSize));
}
catch (ArgumentException ae)
{
return Content(HttpStatusCode.BadRequest, new APIError("404", "Invalid location code"));
}
catch (Exception ex)
{
Logger.LogErrorEvent(ex);
return Content(HttpStatusCode.InternalServerError, new APIError("500", "Error occurred"));
}
}
My as follows LDetailRecord
public class LDetailRecord
{
public DateTime TDateTime { get; set; }
public dynamic Account { get; set; }
public string LCode { get; set; }
public string LName { get; set; }
public string ConfNumber { get; set; }
public decimal WTime { get; set; }
public decimal AssTime { get; set; }
public List<string> RequestedServices { get; set; }
public string PersonRequested { get; set; }
public string AssistedBy { get; set; }
public string CustomerType { get; set; }
public string CheckedInBy { get; set; }
public string Comments { get; set; }
public string PreferredLanguage { get; set; }
}
In my swagger shows as follows
I'm new to the web api and swagger, please help me, what I did wrong here
The answer by #Mikah-Barnett is not entirely correct when it comes to error responses.
Also, because you're returning a different type when there's an error, use the
[ProducesErrorResponseType(typeof(APIError))]
as well. That will let Swagger know you want a different model when there's a client error.
ProducesErrorResponseTypeAttribute(Type) - Is used for API documentation, but can only define a single error type for all errors which are specified with ProducesResponseTypeAttribute(Int32) attribute.
ProducesResponseTypeAttribute(Type, Int32) - Is used for API documentation when you want to have more detailed granularity over all the different types returned, depending on the response status code
As an example, below is what you could define per endpoint. Even better, common response type attributes can be specified at the controller level, meaning you don't need to duplicate for every endpoint.
[HttpPost]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
[ProducesResponseType(typeof(NewOrderResponse), StatusCodes.Status201Created)]
public async Task<IActionResult> Post([FromBody, Required] NewOrderRequest orderRequest)
You need to explicitly state the return type in your methods. So, instead of
public IHttpActionResult GetDetails(...
use
public IHttpActionResult<LDetailRecord> GetDetails(...
That lets OpenAPI know exactly what you're planning to return and it will then show an example of the model in the UI.
Also, because you're returning a different type when there's an error, use the
[ProducesErrorResponseType(typeof(APIError))]
as well. That will let Swagger know you want a different model when there's a client error.
Here's a good article from MSFT documenting how this works, and below is a more complete example (from that article) showing all the pieces together.
/// <summary>
/// Creates a TodoItem.
/// </summary>
/// <remarks>
/// Sample request:
///
/// POST /Todo
/// {
/// "id": 1,
/// "name": "Item1",
/// "isComplete": true
/// }
///
/// </remarks>
/// <param name="item"></param>
/// <returns>A newly created TodoItem</returns>
/// <response code="201">Returns the newly created item</response>
/// <response code="400">If the item is null</response>
[HttpPost]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesErrorResponseType(typeof(APIError))]
public ActionResult<TodoItem> Create(TodoItem item)
{
_context.TodoItems.Add(item);
_context.SaveChanges();
return CreatedAtRoute("GetTodo", new { id = item.Id }, item);
}

c# How to Add Custom Validation Based on Boolean

Here i have a Class Like
Public Class Employee
{
[Required]
public string FName { get; set; }
[Required]
public bool Married { get; set; }
public string WifeName { get; set; }
public string Wife_dOB { get; set; }
}
Here WifeName And Wife_Dob is Required only When Married Is True
Please Help me How can i Resolve this Problem
Im using Here MVC
You can use a custom validation attribute.
You can see bellow a solution that I used a few time ago:
/// <summary>
/// Provides conditional validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
#region Properties
/// <summary>
/// Gets or sets the other property name that will be used during validation.
/// </summary>
/// <value>
/// The other property name.
/// </value>
public string OtherProperty { get; private set; }
/// <summary>
/// Gets or sets the display name of the other property.
/// </summary>
/// <value>
/// The display name of the other property.
/// </value>
public string OtherPropertyDisplayName { get; set; }
/// <summary>
/// Gets or sets the other property value that will be relevant for validation.
/// </summary>
/// <value>
/// The other property value.
/// </value>
public object OtherPropertyValue { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
/// </summary>
/// <value>
/// <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
/// </value>
/// <remarks>
/// How this works
/// - true: validated property is required when other property doesn't equal provided value
/// - false: validated property is required when other property matches provided value
/// </remarks>
public bool IsInverted { get; set; }
/// <summary>
/// Gets a value that indicates whether the attribute requires validation context.
/// </summary>
/// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
public override bool RequiresValidationContext
{
get { return true; }
}
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
/// </summary>
/// <param name="otherProperty">The other property.</param>
/// <param name="otherPropertyValue">The other property value.</param>
public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
: base("'{0}' is required because '{1}' has a value {3}'{2}'.")
{
this.OtherProperty = otherProperty;
this.OtherPropertyValue = otherPropertyValue;
this.IsInverted = false;
}
#endregion
/// <summary>
/// Applies formatting to an error message, based on the data field where the error occurred.
/// </summary>
/// <param name="name">The name to include in the formatted message.</param>
/// <returns>
/// An instance of the formatted error message.
/// </returns>
public override string FormatErrorMessage(string name)
{
return string.Format(
CultureInfo.CurrentCulture,
base.ErrorMessageString,
name,
this.OtherPropertyDisplayName ?? this.OtherProperty,
this.OtherPropertyValue,
this.IsInverted ? "other than " : "of ");
}
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException("validationContext");
}
PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (otherProperty == null)
{
return new ValidationResult(
string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
}
object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);
// check if this value is actually required and validate it
if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
{
if (value == null)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
// additional check for strings so they're not empty
string val = value as string;
if (val != null && val.Trim().Length == 0)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
FluentValidation might be a good choice for your problem. With FluentValidation it would be easy to create an EmployeeValidator class to perform as much or as little validation as needed.
Note: The examples below are written using the standard FluentValidation package since I don't use MVC. There appears to be nuget packages for several versions of MVC in addition to the standard nuget package and your mileage may vary using those implementations.
Here is a very simple example of how to use FluentValidation:
void Main()
{
var emp = new Employee { FName = "John Smith", Married = true };
var val = new EmployeeValidator();
val.ValidateAndThrow(emp);
}
public class Employee
{
public string FName { get; set; }
public bool Married { get; set; }
public string WifeName { get; set; }
public string Wife_dOB { get; set; }
}
public class EmployeeValidator : AbstractValidator<Employee>
{
public EmployeeValidator()
{
RuleFor(e => e.WifeName).NotEmpty().When(e => e.Married);
RuleFor(e => e.Wife_dOB).NotEmpty().When(e => e.Married);
}
}
EDIT
I forgot to include an example of the output from the validation code above.
Example output:
ValidationException: Validation failed:
-- WifeName: 'Wife Name' must not be empty.
-- Wife_dOB: 'Wife_d OB' must not be empty.
An alternative approach, and in my opinion a better one, would be something like this that requires no validation, at least for the Married property:
public class Person
{
public Person(string firstName, string surname, DateTime dateOfBirth)
{
FirstName = firstName;
Surname = surname;
DateOfBirth = dateOfBirth;
}
public string FirstName { get; set; }
public string Surname { get; set; }
public string FullName => $"{FirstName} {Surname}";
public DateTime DateOfBirth { get; }
}
public class Employee : Person
{
public Employee(string firstName, string lastName, DateTime dateOfBirth, DateTime dateOfHire)
: base(firstName, lastName, dateOfBirth)
=> DateOfHire = dateOfHire;
public DateTime DateOfHire { get; }
public Person Spouse { get; set; }
public bool IsMarried => Spouse != null;
}
With this implementation the IsMarried property simply projects whether or not the Spouse property is set. This is purely for convenience but can often be helpful.
Some validation that might make sense for these objects could be the following:
public class PersonValidator : AbstractValidator<IPerson>
{
public PersonValidator()
{
RuleFor(p => p.FirstName).NotEmpty();
RuleFor(p => p.Surname).NotEmpty();
RuleFor(p => p.DateOfBirth).SetValidator(new DateOfBirthValidator());
}
}
public class EmployeeValidator : AbstractValidator<IEmployee>
{
private static readonly DateTime CompanyInceptionDate
= DateTime.Today.Subtract(TimeSpan.FromDays(365.25d * 10d));
public EmployeeValidator()
{
// Person rules
RuleFor(p => p.FirstName).NotEmpty();
RuleFor(p => p.Surname).NotEmpty();
RuleFor(p => p.DateOfBirth).SetValidator(new DateOfBirthValidator());
// Employee rules
RuleFor(e => e.DateOfHire).SetValidator(
// Can't be in the future nor older than the company
new DateRangeValidator(CompanyInceptionDate, DateTime.Today));
}
}
// This class really isn't necessary
// The built-in InclusiveBetween/ExclusiveBetween validators would work just as well
public class DateRangeValidator : PropertyValidator
{
protected const double DaysInAYear = 365.25d;
public DateRangeValidator(DateTime from, DateTime to)
: base($"{{PropertyName}} out of range. Expected between {from:yyyy-MM-dd} and {to:yyyy-MM-dd}.")
{
From = from;
To = to;
}
public DateTime From { get; }
public DateTime To { get; }
protected override bool IsValid(PropertyValidatorContext context)
=> context.PropertyValue is DateTime date
? date >= From && date <= To
: false;
}
public class DateOfBirthValidator : DateRangeValidator
{
private static readonly TimeSpan OneHundredAndFiftyYears
= TimeSpan.FromDays(DaysInAYear * 150d);
public DateOfBirthValidator()
// Can't be in the future nor older than 150 years
: base(DateTime.Today.Subtract(OneHundredAndFiftyYears), DateTime.Today) { }
}

Generic method to get an attribute from a member of a type

I created a convenience method that uses generics to retrieve an attribute applied to a type:
/// <summary>
/// Return an attribute from the provided type if it exists on that type.
/// </summary>
/// <typeparam name="T">The type whose attribute <typeparamref name="TAttType"/>
/// will be returned if it exists on that type.</typeparam>
/// <typeparam name="TAttType">The type of attribute that will be retrieved
/// on <typeparamref name="T"/> if it exists.</typeparam>
/// <returns>Return the attribute with type <typeparamref name="TAttType"/>,
/// if it exists, from target type <typeparamref name="T"/> or else
/// return null.</returns>
public static TAttType GetAttribute<T, TAttType>() where TAttType:Attribute
=> (TAttType) typeof(T).GetCustomAttribute(typeof(TAttType), false);
This only works for attributes on types though. I.e. if I have this attribute:
public class VehicleAttribute : Attribute
{
public string Color { get; }
public int NumWheels { get; }
public VehicleAttribute(string color, int numWheels)
{
Color = color;
NumWheels = numWheels;
}
}
I can do this:
[Vehicle("Yellow", 6)]
public class Bus { }
And then this:
var att = ReflectionTools.GetAttribute<Bus, VehicleAttribute>();
But if I have a property like this (not that it makes sense, but just for demo purposes):
[Vehicle("Blue", 5)]
public string Name { get; set; }
I want to be able to use a similar approach. Is there a way I can use generics to facilitate the retrieval of an attribute from any System.Reflection.MemberInfo, not just System.Type?
You can use a MemberExpression to specify the member you want to get the attribute from. Here is an example:
using System;
using System.Linq.Expressions;
using System.Reflection;
public class VehicleAttribute : Attribute
{
public string Color { get; }
public int NumWheels { get; }
public VehicleAttribute(string color, int numWheels)
{
Color = color;
NumWheels = numWheels;
}
}
[Vehicle("Yellow", 6)]
public class Bus
{
[Vehicle("Blue", 5)]
public string Name { get; set; }
}
public class Program
{
public static void Main()
{
Console.WriteLine(Test<Bus, VehicleAttribute>((x) => x.Name).Color);
}
static U Test<T, U>(Expression<Func<T, object>> expr) where U : Attribute
{
if(!(expr.Body is MemberExpression memberExpr))
{
throw new NotSupportedException("expr");
}
return (U)memberExpr.Member.GetCustomAttribute(typeof(U), false);
}
}

XML Serialization: How to distinguish classes that use the same element name but have a different value in an attribute?

The title may be long, let me explain what I mean. I won't give you the actual XML that I need to work with, but I'll give one that demonstrates the issue I'm facing.
I have an XML that looks like this:
<root>
<creatures>
<creature type="mammal" name="lion">
<sound>roarr</sound>
</creature>
<creature type="bird" name="parrot">
<color>red</color>
</creature>
</creatures>
</root>
Imagine the following classes:
(Of course these are not my real classes either, but you get the point.)
public class Creature
{
public string Name { get; set; }
}
public class MammalCreature : Creature
{
public string Sound { get; set; }
}
public class BirdCreature : Creature
{
public string Color { get; set; }
}
I would like to use XML Serialization with attributes in a manner that I want the serializer to distinguish between the types MammalCreature and BirdCreature by the type attribute.
I've found solutions that can do this by setting the xsi:type attribute to the type name I want, but I'd like to know if there is an actual solution that does this for my case.
Not with just attributes, no. However, this hairy looking code can indeed read your XML properly into a Root class:
[XmlRoot("root")]
public class Root : IXmlSerializable
{
[XmlElement("creatures")]
public Creatures Items { get; set; }
/// <summary>
/// This method is reserved and should not be used. When implementing
/// the IXmlSerializable interface, you should return null (Nothing in
/// Visual Basic) from this method, and instead, if specifying a custom
/// schema is required, apply the
/// <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/>
/// to the class.
/// </summary>
/// <returns>
/// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the
/// XML representation of the object that is produced by the
/// <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/>
/// method and consumed by the
/// <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/>
/// method.
/// </returns>
public XmlSchema GetSchema()
{
return null;
}
/// <summary>
/// Generates an object from its XML representation.
/// </summary>
/// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream
/// from which the object is deserialized. </param>
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement("root");
reader.ReadStartElement("creatures");
List<Creature> creatures = new List<Creature>();
while ((reader.NodeType == XmlNodeType.Element)
&& (reader.Name == "creature"))
{
Creature creature;
string type = reader.GetAttribute("type");
string name = reader.GetAttribute("name");
reader.ReadStartElement("creature");
switch (type)
{
case "mammal":
MammalCreature mammalCreature = new MammalCreature();
reader.ReadStartElement("sound");
mammalCreature.Sound = reader.ReadContentAsString();
reader.ReadEndElement();
creature = mammalCreature;
break;
case "bird":
BirdCreature birdCreature = new BirdCreature();
reader.ReadStartElement("color");
birdCreature.Color = reader.ReadContentAsString();
reader.ReadEndElement();
creature = birdCreature;
break;
default:
reader.Skip();
creature = new Creature();
break;
}
reader.ReadEndElement();
creature.Name = name;
creature.Type = type;
creatures.Add(creature);
}
reader.ReadEndElement();
this.Items = new Creatures();
this.Items.Creature = creatures.ToArray();
reader.ReadEndElement();
}
/// <summary>
/// Converts an object into its XML representation.
/// </summary>
/// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream
/// to which the object is serialized. </param>
public void WriteXml(XmlWriter writer)
{
new XmlSerializer(typeof(Creatures)).Serialize(writer, this.Items);
}
}
[XmlRoot("creatures")]
public class Creatures
{
[XmlElement("creature")]
public Creature[] Creature { get; set; }
}
[XmlInclude(typeof(MammalCreature))]
[XmlInclude(typeof(BirdCreature))]
public class Creature
{
[XmlAttribute("type")]
public string Type { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
}
public class MammalCreature : Creature
{
[XmlElement("sound")]
public string Sound { get; set; }
}
public class BirdCreature : Creature
{
[XmlElement("color")]
public string Color { get; set; }
}
Is it really critical for you to have the same Xml element name for the different types?
Your can end up being much more simple if you can live with the Xml looking like that:
<root>
<creatures>
<MammalCreature name="lion">
<sound>roarr</sound>
</MammalCreature>
<Birdcreature name="parrot">
<color>red</color>
</BirdCreature>
</creatures>
</root>
The XML Serializer does not support this scenario.

Categories