I'm making a BRRES to BFRES converter. For some reason, the MDL0 section seems to be always null when debugging. Here is the code for the Brres file loader:
/// <summary>
/// Represents a set of properties controlling the load of a <see cref="Brres.BrresFile"/>.
/// </summary>
internal class BrresLoaderContext
{
// ---- CONSTRUCTORS -------------------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="BrresLoaderContext"/> class for the given <see cref="BrresFile"/>
/// instance using the given <see cref="BinaryDataReader"/>.
/// </summary>
/// <param name="BrresFile">The <see cref="BrresFile"/> instance to load the data in.</param>
/// <param name="reader">The <see cref="BinaryDataReader"/> to use for reading the data.</param>
internal BrresLoaderContext(BrresFile brresFile, BinaryDataReader reader)
{
BrresFile = brresFile;
Reader = reader;
Warnings = new List<string>();
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the <see cref="BrresFile"/> instance which is loaded to.
/// </summary>
internal BrresFile BrresFile
{
get;
private set;
}
/// <summary>
/// Gets the <see cref="BinaryDataReader"/> which is used to read data from the input stream.
/// </summary>
internal BinaryDataReader Reader
{
get;
private set;
}
/// <summary>
/// Gets or sets the warnings raised after loading the Brres file.
/// </summary>
internal List<string> Warnings
{
get;
private set;
}
}
I mostly didn't update comments.
So why is it null?
Related
For a time-critical media presentation application, it is important that media files be presented right at the instance when the user selects it. Those files reside in a truly humongous directory structure, comprised of thousands of media files.
Clearly, caching the media files in a MemoryStream is the way to go; however, due to the sheer amount of files, it’s not feasible to cache each file entirely. Instead, my idea is to pre-cache a certain buffer of each file, and once the file is presented, play from that cache until the rest of the file is loaded from the hard disk.
What I don’t see is how to “concatenate” both the MemoryStream and the FileStream so as to provide a seamless playback experience. I’m not very strong in data streams (yet), and I see several problems:
How does one keep track of the current read position within the MemoryStream and provide that to the FileStream without the MemoryStream reading more than that?
How does one switch from one stream to the other without having both streams either partially overlap each other or creating a “playback break”?
If using a queue of streams (as suggested in How do I concatenate two System.Io.Stream instances into one?), how can I specify that the second stream has to be ready for read access instantaneously after the first stream is done? Here, in particular, I don’t see how the MemoryStream would help at all, since the FileStream, as second one in the queue, would only begin accessing the hard disk once it’s actually used.
Is it really a feasible approach to have literally hundreds, if not thousands of open streams at once?
Note that I don’t need write access—reading is fully sufficient for the problem at hand. Also, this question is similar to Composite Stream Wrapper providing partial MemoryStream and full original Stream, but the solution provided there is a bug fix for Windows Phone 8 that doesn’t apply in my case.
I’d very much like to widen my rather limited understanding of this, so any help is greatly appreciated.
I would suggest something like the following solution:
Inherit your own CachableFileStream from FileStream
Implement a very simple Cache which uses a data structure you prefer (like a Queue)
Allow Preloading data into the internal cache
Allow Reloading data into the internal cache
Modify the original Read behaviour in a way, that your cache is used
To give you an idea of my idea I would suggest some implementation like the following one:
The usage could be like that:
CachableFileStream cachedStream = new CachableFileStream(...)
{
PreloadSize = 8192,
ReloadSize = 4096,
};
// Force preloading data into the cache
cachedStream.Preload();
...
cachedStream.Read(buffer, 0, buffer.Length);
...
Warning: The code below is neither correctly tested nor ideal - this shall just give you an idea!
The CachableFileStream class:
using System;
using System.IO;
using System.Threading.Tasks;
/// <summary>
/// Represents a filestream with cache.
/// </summary>
public class CachableFileStream : FileStream
{
private Cache<byte> cache;
private int preloadSize;
private int reloadSize;
/// <summary>
/// Gets or sets the amount of bytes to be preloaded.
/// </summary>
public int PreloadSize
{
get
{
return this.preloadSize;
}
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The specified preload size must not be smaller than or equal to zero.");
this.preloadSize = value;
}
}
/// <summary>
/// Gets or sets the amount of bytes to be reloaded.
/// </summary>
public int ReloadSize
{
get
{
return this.reloadSize;
}
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The specified reload size must not be smaller than or equal to zero.");
this.reloadSize = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CachableFileStream"/> class with the specified path and creation mode.
/// </summary>
/// <param name="path">A relative or absolute path for the file that the current CachableFileStream object will encapsulate</param>
/// <param name="mode">A constant that determines how to open or create the file.</param>
/// <exception cref="System.ArgumentException">
/// Path is an empty string (""), contains only white space, or contains one or more invalid characters.
/// -or- path refers to a non-file device, such as "con:", "com1:", "lpt1:", etc. in an NTFS environment.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// Path refers to a non-file device, such as "con:", "com1:", "lpt1:", etc. in a non-NTFS environment.
/// </exception>
/// <exception cref="System.ArgumentNullException">
/// Path is null.
/// </exception>
/// <exception cref="System.Security.SecurityException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="System.IO.FileNotFoundException">
/// The file cannot be found, such as when mode is FileMode.Truncate or FileMode.Open, and the file specified by path does not exist.
/// The file must already exist in these modes.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error, such as specifying FileMode.CreateNew when the file specified by path already exists, occurred.-or-The stream has been closed.
/// </exception>
/// <exception cref="System.IO.DirectoryNotFoundException">
/// The specified path is invalid, such as being on an unmapped drive.
/// </exception>
/// <exception cref="System.IO.PathTooLongException">
/// The specified path, file name, or both exceed the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Mode contains an invalid value
/// </exception>
public CachableFileStream(string path, FileMode mode) : base(path, mode)
{
this.cache = new Cache<byte>();
this.cache.CacheIsRunningLow += CacheIsRunningLow;
}
/// <summary>
/// Reads a block of bytes from the stream and writes the data in a given buffer.
/// </summary>
/// <param name="array">
/// When this method returns, contains the specified byte array with the values between
/// offset and (offset + count - 1) replaced by the bytes read from the current source.
/// </param>
/// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <returns>
/// The total number of bytes read into the buffer. This might be less than the number
/// of bytes requested if that number of bytes are not currently available, or zero
/// if the end of the stream is reached.
/// </returns>
/// <exception cref="System.ArgumentNullException">
/// Array is null.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Offset or count is negative.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The stream does not support reading.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="System.ArgumentException">
/// Offset and count describe an invalid range in array.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// Methods were called after the stream was closed.
/// </exception>
public override int Read(byte[] array, int offset, int count)
{
int readBytesFromCache;
for (readBytesFromCache = 0; readBytesFromCache < count; readBytesFromCache++)
{
if (this.cache.Size == 0)
break;
array[offset + readBytesFromCache] = this.cache.Read();
}
if (readBytesFromCache < count)
readBytesFromCache += base.Read(array, offset + readBytesFromCache, count - readBytesFromCache);
return readBytesFromCache;
}
/// <summary>
/// Preload data into the cache.
/// </summary>
public void Preload()
{
this.LoadBytesFromStreamIntoCache(this.PreloadSize);
}
/// <summary>
/// Reload data into the cache.
/// </summary>
public void Reload()
{
this.LoadBytesFromStreamIntoCache(this.ReloadSize);
}
/// <summary>
/// Loads bytes from the stream into the cache.
/// </summary>
/// <param name="count">The number of bytes to read.</param>
private void LoadBytesFromStreamIntoCache(int count)
{
byte[] buffer = new byte[count];
int readBytes = base.Read(buffer, 0, buffer.Length);
this.cache.AddRange(buffer, 0, readBytes);
}
/// <summary>
/// Represents the event handler for the CacheIsRunningLow event.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">Event arguments.</param>
private void CacheIsRunningLow(object sender, EventArgs e)
{
this.cache.WarnIfRunningLow = false;
new Task(() =>
{
Reload();
this.cache.WarnIfRunningLow = true;
}).Start();
}
}
The Cache class:
using System;
using System.Collections.Concurrent;
/// <summary>
/// Represents a generic cache.
/// </summary>
/// <typeparam name="T">Defines the type of the items in the cache.</typeparam>
public class Cache<T>
{
private ConcurrentQueue<T> queue;
/// <summary>
/// Is executed when the number of items within the cache run below the
/// specified warning limit and WarnIfRunningLow is set.
/// </summary>
public event EventHandler CacheIsRunningLow;
/// <summary>
/// Gets or sets a value indicating whether the CacheIsRunningLow event shall be fired or not.
/// </summary>
public bool WarnIfRunningLow
{
get;
set;
}
/// <summary>
/// Gets or sets a value that represents the lower warning limit.
/// </summary>
public int LowerWarningLimit
{
get;
set;
}
/// <summary>
/// Gets the number of items currently stored in the cache.
/// </summary>
public int Size
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="Cache{T}"/> class.
/// </summary>
public Cache()
{
this.queue = new ConcurrentQueue<T>();
this.Size = 0;
this.LowerWarningLimit = 1024;
this.WarnIfRunningLow = true;
}
/// <summary>
/// Adds an item into the cache.
/// </summary>
/// <param name="item">The item to be added to the cache.</param>
public void Add(T item)
{
this.queue.Enqueue(item);
this.Size++;
}
/// <summary>
/// Adds the items of the specified array to the end of the cache.
/// </summary>
/// <param name="items">The items to be added.</param>
public void AddRange(T[] items)
{
this.AddRange(items, 0, items.Length);
}
/// <summary>
/// Adds the specified count of items of the specified array starting
/// from offset to the end of the cache.
/// </summary>
/// <param name="items">The array that contains the items.</param>
/// <param name="offset">The offset that shall be used.</param>
/// <param name="count">The number of items that shall be added.</param>
public void AddRange(T[] items, int offset, int count)
{
for (int i = offset; i < count; i++)
this.Add(items[i]);
}
/// <summary>
/// Reads one item from the cache.
/// </summary>
/// <returns>The item that has been read from the cache.</returns>
/// <exception cref="System.InvalidOperationException">
/// The cache is empty.
/// </exception>
public T Read()
{
T item;
if (!this.queue.TryDequeue(out item))
throw new InvalidOperationException("The cache is empty.");
this.Size--;
if (this.WarnIfRunningLow &&
this.Size < this.LowerWarningLimit)
{
this.CacheIsRunningLow?.Invoke(this, EventArgs.Empty);
}
return item;
}
/// <summary>
/// Peeks the next item from cache.
/// </summary>
/// <returns>The item that has been read from the cache (without deletion).</returns>
/// <exception cref="System.InvalidOperationException">
/// The cache is empty.
/// </exception>
public T Peek()
{
T item;
if (!this.queue.TryPeek(out item))
throw new InvalidOperationException("The cache is empty.");
return item;
}
}
I hope this helps, have fun ;-)
How to use <inheritdoc/> to inherit specific exception from another method. Is it possible to do something like this:
/// <summary>
/// Summary
/// </summary>
/// <inheritdoc cref="Method2(int)" select="exception[#cref='CustomException']" />
public int Method1()
{
return Method2(1);
}
/// <summary>
/// Summary...
/// </summary>
/// <exception cref="ArgumentException">
/// ArgumentException...
/// </exception>
/// <exception cref="CustomException">
/// CustomException...
/// </exception>
public int Method2(int count)
{
// do something
}
When I build documentation and open it, there is no exception for Method1. Currently, my workaround is to add id="CustomException" to Method2 and use <inheritdoc cref="Method2(int)" select="exception[#id='CustomException']" />
Using C# i'm trying to retrieve the name of the song that is currently playing and display it on a listBox, so every song that plays, it's shown in the listbox.
Using System;
Using WMPLib;
public IWMPMedia currentMedia { get; set; }
private void button1_Click(object sender, EventArgs e)
{
Player = new WMPLib.WindowsMediaPlayer();
string song = Player.currentMedia.name.ToString();
listBox1.Items.Add(song);
}
But it throws me the exception.
"Object reference not set to an instance of an object" here:
string song = Player.currentMedia.name.ToString();
Does anyone knows how to solve this?
You will have to use COM/OLE to do this. I did a program that did exactly that a little while ago, unfortunately tho I cannot find my client code, but I still have the code that implements IOleClientSite/IOleServiceProvider for WMPLib.
I found that code at the following URL:
http://sirayuki.sakura.ne.jp/WmpSample/WmpRemote.zip
Its some code that was writen by Jonathan Dibble, a Microsoft employee if I remember correctly. There is a CHM in the Zip with some explanation on each of the classes.
Heres the code that I still have, in case the link goes down, but like I said I cannot find my code that uses it. It worked almost correctly but I remember that one of the bug left was that it would leave behind wmplayer.exe processes after the media player and my application were closed.
UPDATE!
found some client code and a slighlty modified version of RemotedWindowsMediaPlayer.cs
I just found some coded that I tested this with, and it works. Its from a Winform project, and you need to reference WMPLib for this to work.
In my form I added a button and this code:
/// <summary>
/// Default Constructor
/// </summary>
RemotedWindowsMediaPlayer rm;
public FrmMain()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//Call me old fashioned - I like to do this stuff manually. You can do a drag
//drop if you like, it won't change the results.
rm = new RemotedWindowsMediaPlayer();
rm.Dock = System.Windows.Forms.DockStyle.Top;
panel1.Controls.Add(rm);
return;
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(((WMPLib.IWMPPlayer4)rm.GetOcx()).currentMedia.sourceURL);
}
RemotedWindowsMediaPLayer.cs:
namespace RemoteWMP
{
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using WMPLib;
/// <summary>
/// This is the actual Windows Media Control.
/// </summary>
[System.Windows.Forms.AxHost.ClsidAttribute("{6bf52a52-394a-11d3-b153-00c04f79faa6}")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class RemotedWindowsMediaPlayer : System.Windows.Forms.AxHost,
IOleServiceProvider,
IOleClientSite
{
/// <summary>
/// Used to attach the appropriate interface to Windows Media Player.
/// In here, we call SetClientSite on the WMP Control, passing it
/// the dotNet container (this instance.)
/// </summary>
protected override void AttachInterfaces()
{
try
{
//Get the IOleObject for Windows Media Player.
IOleObject oleObject = this.GetOcx() as IOleObject;
if (oleObject != null)
{
//Set the Client Site for the WMP control.
oleObject.SetClientSite(this as IOleClientSite);
// Try and get the OCX as a WMP player
if (this.GetOcx() as IWMPPlayer4 == null)
{
throw new Exception(string.Format("OCX is not an IWMPPlayer4! GetType returns '{0}'",
this.GetOcx().GetType()));
}
}
else
{
throw new Exception("Failed to get WMP OCX as an IOleObject?!");
}
return;
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
#region IOleServiceProvider Memebers - Working
/// <summary>
/// During SetClientSite, WMP calls this function to get the pointer to <see cref="RemoteHostInfo"/>.
/// </summary>
/// <param name="guidService">See MSDN for more information - we do not use this parameter.</param>
/// <param name="riid">The Guid of the desired service to be returned. For this application it will always match
/// the Guid of <see cref="IWMPRemoteMediaServices"/>.</param>
/// <returns></returns>
IntPtr IOleServiceProvider.QueryService(ref Guid guidService, ref Guid riid)
{
//If we get to here, it means Media Player is requesting our IWMPRemoteMediaServices interface
if (riid == new Guid("cbb92747-741f-44fe-ab5b-f1a48f3b2a59"))
{
IWMPRemoteMediaServices iwmp = new RemoteHostInfo();
return Marshal.GetComInterfaceForObject(iwmp, typeof(IWMPRemoteMediaServices));
}
throw new System.Runtime.InteropServices.COMException("No Interface", (int) HResults.E_NOINTERFACE);
}
#endregion
#region IOleClientSite Members
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException">E_NOTIMPL</exception>
void IOleClientSite.SaveObject()
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
object IOleClientSite.GetMoniker(uint dwAssign, uint dwWhichMoniker)
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
object IOleClientSite.GetContainer()
{
return (int)HResults.E_NOINTERFACE;
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
void IOleClientSite.ShowObject()
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
void IOleClientSite.OnShowWindow(bool fShow)
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
void IOleClientSite.RequestNewObjectLayout()
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
#endregion
/// <summary>
/// Default Constructor.
/// </summary>
public RemotedWindowsMediaPlayer() :
base("6bf52a52-394a-11d3-b153-00c04f79faa6")
{
}
}
}
RemoteHostInfo.cs
using System;
namespace RemoteWMP
{
using System.Runtime.InteropServices;
/// <summary>
/// This class contains the information to return to Media Player about our remote service.
/// </summary>
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class RemoteHostInfo :
IWMPRemoteMediaServices
{
#region IWMPRemoteMediaServices Members
/// <summary>
/// Returns "Remote" to tell media player that we want to remote the WMP application.
/// </summary>
/// <returns></returns>
public string GetServiceType()
{
return "Remote";
}
/// <summary>
/// The Application Name to show in Windows Media Player switch to menu
/// </summary>
/// <returns></returns>
public string GetApplicationName()
{
return System.Diagnostics.Process.GetCurrentProcess().ProcessName;
}
/// <summary>
/// Not in use, see MSDN for more info.
/// </summary>
/// <param name="name"></param>
/// <param name="dispatch"></param>
/// <returns></returns>
public HResults GetScriptableObject(out string name, out object dispatch)
{
name = null;
dispatch = null;
//return (int) HResults.S_OK;//NotImplemented
return HResults.E_NOTIMPL;
}
/// <summary>
/// For skins, not in use, see MSDN for more info.
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public HResults GetCustomUIMode(out string file)
{
file = null;
return HResults.E_NOTIMPL;//NotImplemented
}
#endregion
}
}
COM Interfaces.cs
using System;
namespace RemoteWMP
{
using System.Runtime.InteropServices;
#region Useful COM Enums
/// <summary>
/// Represents a collection of frequently used HRESULT values.
/// You may add more HRESULT VALUES, I've only included the ones used
/// in this project.
/// </summary>
public enum HResults
{
/// <summary>
/// HRESULT S_OK
/// </summary>
S_OK = unchecked((int)0x00000000),
/// <summary>
/// HRESULT S_FALSE
/// </summary>
S_FALSE = unchecked((int)0x00000001),
/// <summary>
/// HRESULT E_NOINTERFACE
/// </summary>
E_NOINTERFACE = unchecked((int)0x80004002),
/// <summary>
/// HRESULT E_NOTIMPL
/// </summary>
E_NOTIMPL = unchecked((int)0x80004001),
/// <summary>
/// USED CLICKED CANCEL AT SAVE PROMPT
/// </summary>
OLE_E_PROMPTSAVECANCELLED = unchecked((int)0x8004000C),
}
/// <summary>
/// Enumeration for <see cref="IOleObject.GetMiscStatus"/>
/// </summary>
public enum DVASPECT
{
/// <summary>
/// See MSDN for more information.
/// </summary>
Content = 1,
/// <summary>
/// See MSDN for more information.
/// </summary>
Thumbnail = 2,
/// <summary>
/// See MSDN for more information.
/// </summary>
Icon = 3,
/// <summary>
/// See MSDN for more information.
/// </summary>
DocPrint = 4
}
/// <summary>
/// Emumeration for <see cref="IOleObject.Close"/>
/// </summary>
public enum TAGOLECLOSE :uint{
OLECLOSE_SAVEIFDIRTY = unchecked((int)0),
OLECLOSE_NOSAVE = unchecked((int)1),
OLECLOSE_PROMPTSAVE = unchecked((int)2)
}
#endregion
#region IWMPRemoteMediaServices
/// <summary>
/// Interface used by Media Player to determine WMP Remoting status.
/// </summary>
[ComImport,
ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("CBB92747-741F-44fe-AB5B-F1A48F3B2A59")]
public interface IWMPRemoteMediaServices
{
/// <summary>
/// Service type.
/// </summary>
/// <returns><code>Remote</code> if the control is to be remoted (attached to WMP.)
/// <code>Local</code>if this is an independent WMP instance not connected to WMP application. If you want local, you shouldn't bother
/// using this control!
/// </returns>
[return: MarshalAs(UnmanagedType.BStr)]
string GetServiceType();
/// <summary>
/// Value to display in Windows Media Player Switch To Application menu option (under View.)
/// </summary>
/// <returns></returns>
[return: MarshalAs(UnmanagedType.BStr)]
string GetApplicationName();
/// <summary>
/// Not in use, see MSDN for details.
/// </summary>
/// <param name="name"></param>
/// <param name="dispatch"></param>
/// <returns></returns>
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
HResults GetScriptableObject([MarshalAs(UnmanagedType.BStr)] out string name,
[MarshalAs(UnmanagedType.IDispatch)] out object dispatch);
/// <summary>
/// Not in use, see MSDN for details.
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
HResults GetCustomUIMode([MarshalAs(UnmanagedType.BStr)] out string file);
}
#endregion
#region IOleServiceProvider
/// <summary>
/// Interface used by Windows Media Player to return an instance of IWMPRemoteMediaServices.
/// </summary>
[ComImport,
GuidAttribute("6d5140c1-7436-11ce-8034-00aa006009fa"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
ComVisible(true)]
public interface IOleServiceProvider
{
/// <summary>
/// Similar to QueryInterface, riid will contain the Guid of an object to return.
/// In our project we will look for <see cref="IWMPRemoteMediaServices"/> Guid and return the object
/// that implements that interface.
/// </summary>
/// <param name="guidService"></param>
/// <param name="riid">The Guid of the desired Service to provide.</param>
/// <returns>A pointer to the interface requested by the Guid.</returns>
IntPtr QueryService(ref Guid guidService, ref Guid riid);
}
/// <summary>
/// This is an example of an INCORRECT entry - do not use, unless you want your app to break.
/// </summary>
[ComImport,
GuidAttribute("6d5140c1-7436-11ce-8034-00aa006009fa"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
ComVisible(true)]
public interface BadIOleServiceProvider
{
/// <summary>
/// This is incorrect because it causes our return interface to be boxed
/// as an object and a COM callee may not get the correct pointer.
/// </summary>
/// <param name="guidService"></param>
/// <param name="riid"></param>
/// <returns></returns>
/// <example>
/// For an example of a correct definition, look at <see cref="IOleServiceProvider"/>.
/// </example>
[return: MarshalAs(UnmanagedType.Interface)]
object QueryService(ref Guid guidService, ref Guid riid);
}
#endregion
#region IOleClientSite
/// <summary>
/// Need to implement this interface so we can pass it to <see cref="IOleObject.SetClientSite"/>.
/// All functions return E_NOTIMPL. We don't need to actually implement anything to get
/// the remoting to work.
/// </summary>
[ComImport,
ComVisible(true),
Guid("00000118-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown) ]
public interface IOleClientSite
{
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
void SaveObject();
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
[return: MarshalAs(UnmanagedType.Interface)]
object GetMoniker(uint dwAssign, uint dwWhichMoniker);
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
[return: MarshalAs(UnmanagedType.Interface)]
object GetContainer();
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
void ShowObject();
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
void OnShowWindow(bool fShow);
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
void RequestNewObjectLayout();
}
#endregion
#region IOleObject
/// <summary>
/// This interface is implemented by WMP ActiveX/COM control.
/// The only function we need is <see cref="SetClientSite"/>.
/// </summary>
[ComImport, ComVisible(true),
Guid("00000112-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleObject
{
/// <summary>
/// Used to pass our custom <see cref="IOleClientSite"/> object to WMP. The object we pass must also
/// implement <see cref="IOleServiceProvider"/> to work right.
/// </summary>
/// <param name="pClientSite">The <see cref="IOleClientSite"/> to pass.</param>
void SetClientSite(IOleClientSite pClientSite);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
IOleClientSite GetClientSite();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void SetHostNames(
[MarshalAs(UnmanagedType.LPWStr)]string szContainerApp,
[MarshalAs(UnmanagedType.LPWStr)]string szContainerObj);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void Close(uint dwSaveOption);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void SetMoniker(uint dwWhichMoniker, object pmk);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
object GetMoniker(uint dwAssign, uint dwWhichMoniker);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void InitFromData(object pDataObject, bool fCreation, uint dwReserved);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
object GetClipboardData(uint dwReserved);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void DoVerb(uint iVerb, uint lpmsg, [MarshalAs(UnmanagedType.Interface)]object pActiveSite,
uint lindex, uint hwndParent, uint lprcPosRect);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
object EnumVerbs();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void Update();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
HResults IsUpToDate();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
Guid GetUserClassID();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.LPWStr)]
string GetUserType(uint dwFormOfType);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void SetExtent(uint dwDrawAspect, [MarshalAs(UnmanagedType.Interface)] object psizel);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
object GetExtent(uint dwDrawAspect);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
uint Advise([MarshalAs(UnmanagedType.Interface)]object pAdvSink);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void Unadvise(uint dwConnection);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
object EnumAdvise();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
uint GetMiscStatus([MarshalAs(UnmanagedType.U4)] DVASPECT dwAspect);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void SetColorScheme([MarshalAs(UnmanagedType.Interface)] object pLogpal);
}
#endregion
}
We have a custom class that inherits from ConfigurationElement called SignalConfigurationElement and defines a bunch of properties using the ConfigurationProperty attribute.
The SignalConfigurationElement class is part of a much larger hierarchy of configuration elements and does not have a constructor. I'm able to retrieve the entire configuration through the ConfigurationManager.GetSection() call, providing the root element's name which is SystemConfiguration, which is defined in the <configSections> of the app.config file.
I don't have control over the custom configuration elements so I can't alter them to provide constructors. I also can't modify the app.config because it's used by a much larger application.
Is it possible to create instances or collections of SignalConfigurationElement given an XML string of <signals> entries? The class doesn't have a constructor so I'm assuming the ConfigurationManager.GetSection call used by our other applications to retrieve the entire configuration (not what I want) uses reflection to create its instances.
Code (Can't change any of this at all.):
App.Config
<configSections>
<section name="SystemConfiguration" type="Fully.Qualified.Namespace.SystemConfiguration, SystemConfiguration"/>
</configSections>
<SystemConfiguration id="System1" name="System 1">
<equipments>
<clear />
<add id="Equipment11" equipmentType="EQ">
<signals>
<clear />
<add id="EQ11Signal1" signalId="EQ11Signal1" type="Version" />
<add id="EQ11Signal2" signalId="EQ11Signal2" type="Status" />
<add id="EQ11Signal3" signalId="EQ11Signal3" type="Status" />
</signals>
</add>
<add id="Equipment21" equipmentType="EQ">
<signals>
<clear />
<add id="EQ21Signal1" signalId="EQ21Signal1" type="Version" />
<add id="EQ21Signal2" signalId="EQ21Signal2" type="Status" />
<add id="EQ21Signal3" signalId="EQ21Signal3" type="Status" />
</signals>
</add>
</equipments>
<!-- And a whole lot more. Somewhere in the avenue of 30,000 <signals> entries.-->
</SystemConfiguration>
Classes:
public class SystemConfigurationSection : ConfigurationSection
{
/// <summary>
/// Determines the XML tag that will contain this Configuration Section in an .config file.
/// </summary>
public const string SystemConfigurationSectionName = "SystemConfiguration";
/// <summary>
/// Instance factory method for an instance of the Configuration Section creation.
/// </summary>
/// <returns>
/// Instance of the System Configuration section created based on the .config file of the application.
/// </returns>
public static SystemConfigurationSection GetSystemConfigurationSection()
{
SystemConfigurationSection result =
(SystemConfigurationSection) ConfigurationManager.GetSection(SystemConfigurationSectionName);
return result;
}
/// <summary>
/// Represents the XML attribute used to store ID of the System.
/// </summary>
[ConfigurationProperty(IdConfigurationElementName, IsRequired = true)]
public string Id
{
get { return (string) this[IdConfigurationElementName]; }
set { this[IdConfigurationElementName] = value; }
}
/// <summary>
/// Determines name of the XML attribute that will contain ID of the System.
/// </summary>
public const string IdConfigurationElementName = "id";
/// <summary>
/// Represents the XML attribute used to store Name of the System.
/// </summary>
[ConfigurationProperty(NameConfigurationElementName, IsRequired = true)]
public string Name
{
get { return (string) this[NameConfigurationElementName]; }
set { this[NameConfigurationElementName] = value; }
}
/// <summary>
/// Determines name of the XML attribute that will contain Name of the System.
/// </summary>
public const string NameConfigurationElementName = "name";
/// <summary>
/// Represents the XML attribute used to store Description of the System
/// </summary>
[ConfigurationProperty(DescriptionConfigurationElementName, IsRequired = false, DefaultValue = "")]
public string Description
{
get { return (string) this[DescriptionConfigurationElementName]; }
set { this[DescriptionConfigurationElementName] = value; }
}
/// <summary>
/// Determines name of the XML attribute that will contain Name of the System.
/// </summary>
public const string DescriptionConfigurationElementName = "description";
/// <summary>
/// Represents the collection of the System's Equipments as they are described in the .config file.
/// </summary>
[ConfigurationProperty(EquipmentsConfigurationElementCollectionName, IsDefaultCollection = false)]
[ConfigurationCollection(typeof (EquipmentConfigurationElementCollection), AddItemName = "add",
ClearItemsName = "clear", RemoveItemName = "remove")]
public EquipmentConfigurationElementCollection Equipments
{
get { return (EquipmentConfigurationElementCollection) base[EquipmentsConfigurationElementCollectionName]; }
}
/// <summary>
/// Determines name of the XML tag that will contain collection of the System's Equipments.
/// </summary>
public const string EquipmentsConfigurationElementCollectionName = "equipments";
}
/// <summary>
/// Extends the standard .Net ConfigurationElementCollection re-definind commectio manipulation members and making them strongly-typed.
/// </summary>
/// <typeparam name="TElementType">Type of the Configuration Elements that can be included into the collection.</typeparam>
public class ConfigurationElementCollectionBase<TElementType> : ConfigurationElementCollection, IEnumerable<TElementType>
where TElementType : ConfigurationElement, new()
{
/// <summary>
/// Makes the addition operation public.
/// </summary>
/// <param name="customElement">Configuration element to add to the collection.</param>
public virtual void Add(TElementType customElement)
{
BaseAdd(customElement);
}
/// <summary>
/// Overrides the base implementation of the overloaded method masking an exception throwing.
/// </summary>
/// <param name="element">Configuration element to add.</param>
protected override void BaseAdd(ConfigurationElement element)
{
BaseAdd(element, false);
}
/// <summary>
/// Overrides the base property hardcoding the returned value.
/// </summary>
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
}
/// <summary>
/// Overrides the base implementation of the instance factory method.
/// </summary>
/// <returns>A new instance of the Configuration Element type determined by the type parameter.</returns>
protected override ConfigurationElement CreateNewElement()
{
return new TElementType();
}
/// <summary>
/// Overrides the base implementation of the method determining the indexing algorithm used in the collection.
/// </summary>
/// <param name="element">The configuration element to get index of.</param>
/// <returns></returns>
protected override object GetElementKey(ConfigurationElement element)
{
return ((TElementType)element);
}
/// <summary>
/// Collection's element accessor by index property.
/// </summary>
/// <param name="index">Index of the desired element in the collection.</param>
/// <returns>The requested collection element if exists.</returns>
public TElementType this[int index]
{
get { return (TElementType)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
/// <summary>
/// Overrides the collection's element accessor by key property.
/// </summary>
/// <param name="id">Key of the desired collection element.</param>
/// <returns>The requested collection element if exists</returns>
public new TElementType this[string id]
{
get { return (TElementType)BaseGet(id); }
}
/// <summary>
/// Implements a standard collection method looking for an element in the collection and returning the element's index if found.
/// </summary>
/// <param name="element">The element to look for.</param>
/// <returns>Index of the element in the collection if exists.</returns>
public int GetIndexOf(TElementType element)
{
return BaseIndexOf(element);
}
/// <summary>
/// Implements a standard collection method removing an element from the collection.
/// </summary>
/// <param name="url">The element to be removed from the collection.</param>
public void Remove(TElementType url)
{
if (BaseIndexOf(url) >= 0)
BaseRemove(url);
}
/// <summary>
/// Implements the standard collection member removing an element from the collection by the element's index.
/// </summary>
/// <param name="index">Index of the element to be removed from the collection.</param>
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
/// <summary>
/// Implements a standard collection method removing an element by the element's key.
/// </summary>
/// <param name="id">Key of the element to be removed from the collection.</param>
public void Remove(string id)
{
BaseRemove(id);
}
/// <summary>
/// Implements the standard collection method that clears the collection.
/// </summary>
public void Clear()
{
BaseClear();
}
public new IEnumerator<TElementType> GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return this[i];
}
}
public class EquipmentConfigurationElement : ConfigurationElement
{
/// <summary>
/// Represents the collection of the Equipment Unit's Signals as they are described in the .config file.
/// </summary>
[ConfigurationProperty(signalsConfigurationElementCollectionName, IsDefaultCollection = false)]
[ConfigurationCollection(typeof(SignalConfigurationElementCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
public SignalConfigurationElementCollection Signals
{
get
{
return (SignalConfigurationElementCollection)base[signalsConfigurationElementCollectionName];
}
}
/// <summary>
/// Determines name of the XML tag that will contain collection of the Equipment Unit's Signals.
/// </summary>
private const string signalsConfigurationElementCollectionName = "signals";
}
/// <summary>
/// Represents a type-safe collection of Equipment Unit Configuration Elements.
/// </summary>
public class EquipmentConfigurationElementCollection : ConfigurationElementCollectionBase<EquipmentConfigurationElement>
{
}
/// <summary>
/// Represensts a Signal's configuration element
/// </summary>
/// <remarks>
/// As the class is derived from ConfigurationElementBase, a Signal's configuration element will expect "is", "name", and "description" XML attributes defined in the configuration file.
/// </remarks>
public sealed class SignalConfigurationElement : ConfigurationElement
{
/// <summary>
/// Represents an XML attribute used to determine type of the Signal.
/// </summary>
/// <remarks>
/// The attribute is expected to have a string value which is equal to one of SignalType enumeration member names: "Status" or "Command".
/// </remarks>
[ConfigurationProperty(typeConfigurationElementName, IsRequired = false, DefaultValue = "status")]
public string Type
{
get { return (string) this[typeConfigurationElementName]; }
set { this[typeConfigurationElementName] = value; }
}
/// <summary>
/// Determines name of the XML attribute that will contain type of the Signal.
/// </summary>
private const string typeConfigurationElementName = "type";
}
/// <summary>
/// Represents a type-safe collection of Signal Configuration Elements.
/// </summary>
public class SignalConfigurationElementCollection : ConfigurationElementCollectionBase<SignalConfigurationElement>
{
}
This is possible through the dark arts of reflection. Given the XML shown in your question, you can do this:
var configXml = GetAppConfigXml(); // Returns the XML shown in your question.
var config = (SystemConfigurationSection)Activator.CreateInstance(typeof(SystemConfigurationSection), true);
using (var sr = new StringReader(configXml))
using (var xmlReader = XmlReader.Create(sr))
{
if (xmlReader.ReadToDescendant("SystemConfiguration"))
using (var subReader = xmlReader.ReadSubtree())
{
var dynMethod = config.GetType().GetMethod("DeserializeSection", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(XmlReader) }, null);
dynMethod.Invoke(config, new object[] { subReader });
}
}
Debug.Assert(config.Equipments.Count == 2); // No assert
Reference: source for ConfigurationSection.DeserializeSection.
Incidentally, the configuration classes attached to your question don't match the XML. Some properties are missing, and the properties that correspond to attributes in the XML must have IsKey = true. If the XML doesn't match the configuration classes, DeserializeSection will throw an exception.
I have 3 entities.
Let's say I have Event entity, and 2 "derived" entities: Accident, Repair.
They provide some additional fields over Event entity.
Event have StartDate and EndDate which are always required so I mark them with [Required] attribute. That's ok. But I have some additional validation logic that checks if the Event is Repair, then some other Event fields are also required. For this I provide custom validator.
The problem is that the properties marked with [Required] attribute are always checked before other validators.
What I want achieve:
If Event is Accident I want to make Event.SomeField required.
Validation summary should show now contain 3 validation errors at the very first validation attempt.
How it behaves now:
If Event is Accident first validation attempt shows 2 errors of 2 properties marked as [Required]. Only after I fill those, on the next validation attempt fires my custom validator which also states that 3rd Event.SomeField is also required.
I want all the required fields to validate at the same time.
Is this possible? How to achieve this?
For the case that a property is required based on a certain condition, i use a custom attribute, that was initially provided by Jeff Handley in his blog.
Here´s the code of the Attribute:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true)]
public class ConditionallyRequiredAttribute : RequiredAttribute {
private MemberInfo _member;
/// <summary>
/// The name of the member that will return the state that indicates
/// whether or not the validated member is required.
/// </summary>
public string ConditionMember { get; private set; }
/// <summary>
/// The condition value under which this validator treats
/// the affected member as required.
/// </summary>
public object RequiredCondition { get; private set; }
/// <summary>
/// Comma-separated list of additional members to
/// add to validation errors. By default, the
/// <see cref="ConditionMember"/> is added.
/// </summary>
public string ErrorMembers { get; set; }
/// <summary>
/// Conditionally require a value, only when the specified
/// <paramref name="conditionMember"/> is <c>true</c>.
/// </summary>
/// <param name="conditionMember">
/// The member that must be <c>true</c> to require a value.
/// </param>
public ConditionallyRequiredAttribute(string conditionMember)
: this(conditionMember, true) { }
/// <summary>
/// Conditionally require a value, only when the specified
/// <paramref name="conditionMember"/> has a value that
/// exactly matches the <paramref name="requiredCondition"/>.
/// </summary>
/// <param name="conditionMember">
/// The member that will be evaluated to require a value.
/// </param>
/// <param name="requiredCondition">
/// The value the <paramref name="conditionMember"/> must
/// hold to require a value.
/// </param>
public ConditionallyRequiredAttribute(string conditionMember, object requiredCondition) {
this.ConditionMember = conditionMember;
this.RequiredCondition = requiredCondition;
this.ErrorMembers = this.ConditionMember;
}
/// <summary>
/// Override the base validation to only perform validation when the required
/// condition has been met. In the case of validation failure, augment the
/// validation result with the <see cref="ErrorMembers"/> as an additional
/// member names, as needed.
/// </summary>
/// <param name="value">The value being validated.</param>
/// <param name="validationContext">The validation context being used.</param>
/// <returns>
/// <see cref="ValidationResult.Success"/> if not currently required or if satisfied,
/// or a <see cref="ValidationResult"/> in the case of failure.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if (this.DiscoverMember(validationContext.ObjectType)) {
object state = this.InvokeMember(validationContext.ObjectInstance);
// We are only required if the current state
// matches the specified condition.
if (Object.Equals(state, this.RequiredCondition)) {
ValidationResult result = base.IsValid(value, validationContext);
if (result != ValidationResult.Success && this.ErrorMembers != null && this.ErrorMembers.Any()) {
result = new ValidationResult(result.ErrorMessage,
result.MemberNames.Union(this.ErrorMembers.Split(',').Select(s => s.Trim())));
}
return result;
}
return ValidationResult.Success;
}
throw new InvalidOperationException(
"ConditionallyRequiredAttribute could not discover member: " + this.ConditionMember);
}
/// <summary>
/// Discover the member that we will evaluate for checking our condition.
/// </summary>
/// <param name="objectType"></param>
/// <returns></returns>
private bool DiscoverMember(Type objectType) {
if (this._member == null) {
this._member = (from member in objectType.GetMember(this.ConditionMember).Cast<MemberInfo>()
where IsSupportedProperty(member) || IsSupportedMethod(member)
select member).SingleOrDefault();
}
// If we didn't find 1 exact match, indicate that we could not discover the member
return this._member != null;
}
/// <summary>
/// Determine if a <paramref name="member"/> is a
/// method that accepts no parameters.
/// </summary>
/// <param name="member">The member to check.</param>
/// <returns>
/// <c>true</c> if the member is a parameterless method.
/// Otherwise, <c>false</c>.
/// </returns>
private bool IsSupportedMethod(MemberInfo member) {
if (member.MemberType != MemberTypes.Method) {
return false;
}
MethodInfo method = (MethodInfo)member;
return method.GetParameters().Length == 0
&& method.GetGenericArguments().Length == 0
&& method.ReturnType != typeof(void);
}
/// <summary>
/// Determine if a <paramref name="member"/> is a
/// property that has no indexer.
/// </summary>
/// <param name="member">The member to check.</param>
/// <returns>
/// <c>true</c> if the member is a non-indexed property.
/// Otherwise, <c>false</c>.
/// </returns>
private bool IsSupportedProperty(MemberInfo member) {
if (member.MemberType != MemberTypes.Property) {
return false;
}
PropertyInfo property = (PropertyInfo)member;
return property.GetIndexParameters().Length == 0;
}
/// <summary>
/// Invoke the member and return its value.
/// </summary>
/// <param name="objectInstance">The object to invoke against.</param>
/// <returns>The member's return value.</returns>
private object InvokeMember(object objectInstance) {
if (this._member.MemberType == MemberTypes.Method) {
MethodInfo method = (MethodInfo)this._member;
return method.Invoke(objectInstance, null);
}
PropertyInfo property = (PropertyInfo)this._member;
return property.GetValue(objectInstance, null);
}
#if !SILVERLIGHT
/// <summary>
/// The desktop framework has this property and it must be
/// overridden when allowing multiple attributes, so that
/// attribute instances can be disambiguated based on
/// field values.
/// </summary>
public override object TypeId {
get { return this; }
}
#endif
}
Here´s an example:
public class Dummy{
public bool IsCondition {get; set;}
[ConditionallyRequired("IsCondition", true)]
public string SometimesRequired {get; set;}
}