I am creating a new Asp.Net Core solution based on an existing Asp.Net 4.5 solution.
The current solution uses Microsoft Unity Container and the Infrastructure has references to the Service Locator.
I want to get rid of the Service Locator and avoid referencing specific DI containers in my new Infrastructure.
I'm having an issue coming up with a good way to replace the current Command/Query/Event Dispatcher without any DI container dependencies.
Here is my Dispatcher class
public class Dispatcher : IDispatcher
{
private const string HandleMethodName = "Handle";
public TResponse Request<TResponse>(IQuery<TResponse> query)
{
Type queryType = query.GetType();
// used for when OperationResult<object> was used
Type operationResultTrueReturnType = typeof(TResponse);
if (operationResultTrueReturnType == typeof(object))
{
operationResultTrueReturnType = queryType.GetInterface(typeof(IQuery<>).Name).GenericTypeArguments[0];
}
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), operationResultTrueReturnType);
return ExecuteHandler<TResponse>(handlerType, query, queryType);
}
public OperationResult Submit(ICommand command)
{
Type commandType = command.GetType();
var baseTypeAttribute = (CommandBaseTypeAttribute)commandType.GetCustomAttributes(typeof(CommandBaseTypeAttribute), false).FirstOrDefault();
if (baseTypeAttribute != null)
commandType = baseTypeAttribute.BaseType;
try
{
Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
return ExecuteHandler<OperationResult>(handlerType, command, commandType);
}
catch (InvalidOperationException ex)
{
return new OperationResult(OperationResultStatus.Failure, ex.Message);
}
}
public OperationResult<TResult> Submit<TResult>(ICommand<TResult> command)
{
Type commandType = command.GetType();
var baseTypeAttribute = (CommandBaseTypeAttribute)commandType.GetCustomAttributes(typeof(CommandBaseTypeAttribute), false).FirstOrDefault();
if (baseTypeAttribute != null)
commandType = baseTypeAttribute.BaseType;
try
{
Type handlerType = typeof(ICommandHandler<,>).MakeGenericType(commandType, typeof(TResult));
return ExecuteHandler<OperationResult<TResult>>(handlerType, command, commandType);
}
catch (InvalidOperationException ex)
{
return new OperationResult<TResult>(OperationResultStatus.Failure, default(TResult), ex.Message);
}
}
public void Raise(IDomainEvent domainEvent)
{
Type domainEventType = domainEvent.GetType();
try
{
Type handlerType = typeof(ICommandHandler<>).MakeGenericType(domainEventType);
ExecuteHandler(handlerType, domainEvent, domainEventType);
}
catch (InvalidOperationException)
{
}
}
private static void ExecuteHandler(Type handlerType, object argument, Type argumentType)
{
object handler = ServiceLocator.Current.GetInstance(handlerType);
if (handler == null)
throw new ConfigurationErrorsException("Handler not registered for type " + argumentType.Name);
try
{
MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
handleMethod.Invoke(handler, new[] { argument });
}
catch (TargetInvocationException ex)
{
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
}
private static TReturnValue ExecuteHandler<TReturnValue>(Type handlerType, object argument, Type argumentType)
{
object handler = ServiceLocator.Current.GetInstance(handlerType);
if (handler == null)
throw new ConfigurationErrorsException("Handler not registered for type " + argumentType.Name);
try
{
MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
return (TReturnValue)handleMethod.Invoke(handler, new[] { argument });
}
catch (TargetInvocationException ex)
{
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
}
}
ExecuteHandler has the ServiceLocator call.
How can I handle this without using it?
I like the suggestion provided in the comments. If you want to abstract away the service locator then have the dispatcher explicitly depend on an abstract service provider (like IServiceProvider) that will be used to do resolutions.
public class Dispatcher : IDispatcher {
private readonly IServiceProvider serviceProvider;
public Dispatcher (IServiceProvider serviceProvider) {
this.serviceProvider = serviceProvider;
}
//...other code removed for brevity
private object GetService(Type serviceType) {
return serviceProvider.GetService(serviceType);
}
private void ExecuteHandler(Type handlerType, object argument, Type argumentType) {
object handler = GetService(handlerType);
if (handler == null)
throw new ConfigurationErrorsException("Handler not registered for type " + argumentType.Name);
try {
MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
handleMethod.Invoke(handler, new[] { argument });
} catch (TargetInvocationException ex) {
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
}
private TReturnValue ExecuteHandler<TReturnValue>(Type handlerType, object argument, Type argumentType) {
object handler = GetService(handlerType);
if (handler == null)
throw new ConfigurationErrorsException("Handler not registered for type " + argumentType.Name);
try {
MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
return (TReturnValue)handleMethod.Invoke(handler, new[] { argument });
} catch (TargetInvocationException ex) {
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
}
}
The dispatcher is now no longer tightly coupled to the service locator anti-pattern and allows for any derived provider to be used. This allows you to avoid referencing specific DI containers.
I added an IServiceProvider to the Dispatcher's constructor.
public class Dispatcher : IDispatcher
{
private const string HandleMethodName = "Handle";
private readonly IServiceProvider _serviceProvider;
public Dispatcher(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public TResponse Request<TResponse>(IQuery<TResponse> query)
{
Type queryType = query.GetType();
// used for when OperationResult<object> was used
Type operationResultTrueReturnType = typeof(TResponse);
if (operationResultTrueReturnType == typeof(object))
{
operationResultTrueReturnType = queryType.GetInterface(typeof(IQuery<>).Name).GenericTypeArguments[0];
}
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), operationResultTrueReturnType);
return ExecuteHandler<TResponse>(handlerType, query, queryType);
}
public OperationResult Submit(ICommand command)
{
Type commandType = command.GetType();
var baseTypeAttribute = (CommandBaseTypeAttribute)commandType.GetCustomAttributes(typeof(CommandBaseTypeAttribute), false).FirstOrDefault();
if (baseTypeAttribute != null)
commandType = baseTypeAttribute.BaseType;
try
{
Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
return ExecuteHandler<OperationResult>(handlerType, command, commandType);
}
catch (InvalidOperationException ex)
{
return new OperationResult(OperationResultStatus.Failure, ex.Message);
}
}
public OperationResult<TResult> Submit<TResult>(ICommand<TResult> command)
{
Type commandType = command.GetType();
var baseTypeAttribute = (CommandBaseTypeAttribute)commandType.GetCustomAttributes(typeof(CommandBaseTypeAttribute), false).FirstOrDefault();
if (baseTypeAttribute != null)
commandType = baseTypeAttribute.BaseType;
try
{
Type handlerType = typeof(ICommandHandler<,>).MakeGenericType(commandType, typeof(TResult));
return ExecuteHandler<OperationResult<TResult>>(handlerType, command, commandType);
}
catch (InvalidOperationException ex)
{
return new OperationResult<TResult>(OperationResultStatus.Failure, default(TResult), ex.Message);
}
}
private TReturnValue ExecuteHandler<TReturnValue>(Type handlerType, object argument, Type argumentType)
{
object handler = _serviceProvider.GetService(handlerType);
if (handler == null)
throw new ArgumentException("Handler not registered for type " + argumentType.Name);
try
{
MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
return (TReturnValue)handleMethod.Invoke(handler, new[] { argument });
}
catch (TargetInvocationException ex)
{
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
}
}
Then, injected it in Startup on the client.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ResolutionDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
// Domain Event Handlers
services.AddTransient<IEventHandler<RequestCreatedEvent>, RequestCreatedHandler>();
// Domain Event Dispatcher
services.AddSingleton<IDomainEventDispatcher, DomainEventDispatcher>();
// Units of Work
services.AddTransient<IResolutionUnitOfWork, ResolutionUnitOfWork>();
// Commands and Queries
services.AddTransient<ICommandHandler<CreateRequestCommand, Guid>, CreateRequestHandler>();
// Command and Query Dispatcher
services.AddSingleton<IDispatcher, Dispatcher>();
}
Related
I have the following scenario:
I have several derived exceptions classes that implements a base exception
//Base exception type
public class SaberpsicologiaException : Exception
{
}
//One of the derived exception class
public class MovedPermanentlyException : SaberpsicologiaException
{
public string CannonicalUri { get; private set; }
public MovedPermanentlyException(string cannonicalUri)
: base($"Moved permanently to {cannonicalUri}")
{
this.CannonicalUri = cannonicalUri;
}
}
For each exception class I want to implement an exceptionHandler that will return an ActionResult, which will implement a common interface:
interface ISaberpsicologiaExceptionHandler<T>
where T : SaberpsicologiaException
{
ActionResult Result(T exception);
}
public class MovedPermanentlyExceptionHandler
: ISaberpsicologiaExceptionHandler<MovedPermanentlyException>
{
public ActionResult Result(MovedPermanentlyException exception)
{
var redirectResult = new RedirectResult(exception.CannonicalUri);
redirectResult.Permanent = true;
return redirectResult;
}
}
When I catch an exception derived from SaberpsicologiaException I want the appropiate handler to run:
public class ExceptionHandlerFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
base.OnException(context);
HandleResponseCodeByExceptionType(context);
}
private void HandleResponseCodeByExceptionType(ExceptionContext context)
{
var exception = context.Exception;
if (!CanHandle(exception))
{
return;
}
var mapping = new Dictionary<Type, Type>
{
{ typeof(MovedPermanentlyException), typeof(MovedPermanentlyExceptionHandler) }
};
var handlerType = mapping[exception.GetType()];
var handler = Activator.CreateInstance(handlerType);
handler.Result(exception); //<- compilation error
//handler is type "object" and not MovedPermanentlyExceptionHandler
}
}
I tried to resolve it with the Activator (Reflection), but I get to the problem of not really having and object of type ISaberpsicologiaExceptionHandler< [runtime exceptiontype] > so I can't have use the type properly.
In summary the problem is that I have an exception type and I want to get the ISaberpsicologiaExceptionHandler for that exception type, I guess I could use more reflection to just execute the 'Result' method, but I would like to do it a little bit more ellegant.
You didn't show the full context of your class that implements ISaberpsicologiaExceptionHandler<T>. But just from the definition of this interface I would say it doesn't need to be a generic interface.
A few possible solutions:
Solution 1
Make the method generic:
interface ISaberpsicologiaExceptionHandler
{
ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException;
}
public class MovedPermanentlyExceptionHandler : ISaberpsicologiaExceptionHandler
{
public ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException
{
if (exception is MovedPermanentlyException movedPermanentlyException)
{
var redirectResult = new RedirectResult(movedPermanentlyException.CannonicalUri);
redirectResult.Permanent = true;
return redirectResult;
}
throw new InvalidArgumentException("Exception type not supported", nameof(exception));
}
}
Usage:
To access ISaberpsicologiaExceptionHandler.Result just cast to the non-generic base interface ISaberpsicologiaExceptionHandler no matter the implementing type
catch (MovedPermanentlyException exception)
{
var handler = Activator.CreateInstance(handlerType) as ISaberpsicologiaExceptionHandler;
handler.Result(exception);
}
Solution 2
Use specialized interfaces:
// General interface
interface ISaberpsicologiaExceptionHandler
{
ActionResult Result(Exception exception);
}
// Specialized interface
interface IMovedPermanentlyExceptionHandler : ISaberpsicologiaExceptionHandler
{
ActionResult Result(MovedPermanentlyException exception);
}
public class MovedPermanentlyExceptionHandler : IMovedPermanentlyExceptionHandler
{
public ActionResult Result(MovedPermanentlyException exception)
{
var redirectResult = new RedirectResult(exception.CannonicalUri);
redirectResult.Permanent = true;
return redirectResult;
}
#region Implementation of ISaberpsicologiaExceptionHandler
// Explicit interface implementation
ActionResult ISaberpsicologiaExceptionHandler.Result(Exception exception)
{
if (exception is MovedPermanentlyException movedPermanentlyException)
{
return Result(movedPermanentlyException);
}
throw new InvalidArgumentException("Exception type not supported", nameof(exception));
}
#endregion
}
Usage:
To access ISaberpsicologiaExceptionHandler.Result just cast to the non-generic less specialized base interface ISaberpsicologiaExceptionHandler no matter the implementing type.
catch (MovedPermanentlyException exception)
{
var handler = Activator.CreateInstance(handlerType) as ISaberpsicologiaExceptionHandler;
handler.Result(exception);
}
Solution 3
Use reflection:
interface ISaberpsicologiaExceptionHandler
{
ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException;
}
public class MovedPermanentlyExceptionHandler : ISaberpsicologiaExceptionHandler
{
public ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException
{
if (exception is MovedPermanentlyException movedPermanentlyException)
{
var redirectResult = new RedirectResult(movedPermanentlyException.CannonicalUri);
redirectResult.Permanent = true;
return redirectResult;
}
throw new InvalidArgumentException("Exception type not supported", nameof(exception));
}
}
Usage:
To access ISaberpsicologiaExceptionHandler.Result just cast to the non-generic base interface ISaberpsicologiaExceptionHandler no matter the implementing type
catch (MovedPermanentlyException exception)
{
var handler = Activator.CreateInstance(handlerType) as ISaberpsicologiaExceptionHandler;
MethodInfo reflectedMethod = handlerType.GetMethod("Result");
MethodInfo genericMethod = reflectedMethod.MakeGenericMethod(exception.GetType());
object[] args = {exception};
genericMethod.Invoke(this, args);
}
Solution 4
Recommended solution.
Use the proper concrete implementation on invocation:
I don't know the concept of your exception handler. But since you always know which specific exception you want to catch you can create the proper instance (using a factory at this point is also an option):
try
{
// Do something that can throw a MovedPermanentlyException
}
catch (MovedPermanentlyException e)
{
var movedPermanentlyExceptionHandler = new MovedPermanentlyExceptionHandler();
movedPermanentlyExceptionHandler.Result(e);
}
catch (SomeOtherException e)
{
var someOtherExceptionHandler = new SomeOtherExceptionHandler();
someOtherExceptionHandler.Result(e);
}
There are more solutions so I just take a break. It just boils down to avoid code that uses unknown generic types where members of this unknown type are referenced. I argue that this is always possible and just a question of good design.
I took a more generic approach using System.Linq.Expressions
Given an exception type, a delegate is built to invoke the desired function
LambdaExpression buildHandlerDelegate(Type exceptionType, Type handlerType) {
var type = typeof(ISaberpsicologiaExceptionHandler<>);
var genericType = type.MakeGenericType(exceptionType); //ISaberpsicologiaExceptionHandler<MyException>
var handle = genericType.GetMethod("Result", new[] { exceptionType });
var func = typeof(Func<,>);
var delegateType = func.MakeGenericType(typeof(Exception), typeof(ActionResult));
//Intension is to create the following expression:
// Func<Exception, ActionResult> function =
// (exception) => (new handler()).Result((MyException)exception);
// exception =>
var exception = Expression.Parameter(typeof(Exception), "exception");
// new handler()
var newHandler = Expression.New(handlerType);
// (MyException)exception
var cast = Expression.Convert(exception, exceptionType);
// (new handler()).Result((MyException)exception)
var body = Expression.Call(newHandler, handle, cast);
//Func<TException, ActionResult> (exception) =>
// (new handler()).Result((MyException)exception)
var expression = Expression.Lambda(delegateType, body, exception);
return expression;
}
and can be used like the following with the filter
//...
var exceptionType = exception.GetType();
var handlerType = mapping[exceptionType];
var handler = buildHandlerDelegate(exceptionType, handlerType).Compile();
var result = handler.DynamicInvoke(exception);
context.Result = (IActionResult)result;
//...
Here is the full implementation
public class ExceptionHandlerFilter : ExceptionFilterAttribute {
public override void OnException(ExceptionContext context) {
base.OnException(context);
HandleResponseCodeByExceptionType(context);
}
static readonly Dictionary<Type, Type> mapping = new Dictionary<Type, Type>
{
{ typeof(MovedPermanentlyException), typeof(MovedPermanentlyExceptionHandler) }
};
private void HandleResponseCodeByExceptionType(ExceptionContext context) {
var exception = context.Exception;
if (!CanHandle(exception)) {
return;
}
var exceptionType = exception.GetType();
var handlerType = mapping[exceptionType];
var handler = buildHandlerDelegate(exceptionType, handlerType).Compile();
var result = handler.DynamicInvoke(exception);
context.Result = (IActionResult)result;
}
LambdaExpression buildHandlerDelegate(Type exceptionType, Type handlerType) {
var type = typeof(ISaberpsicologiaExceptionHandler<>);
var genericType = type.MakeGenericType(exceptionType); //ISaberpsicologiaExceptionHandler<MyException>
var handle = genericType.GetMethod("Result", new[] { exceptionType });
var func = typeof(Func<,>);
var delegateType = func.MakeGenericType(typeof(Exception), typeof(ActionResult));
//Intension is to create the following expression:
// Func<Exception, ActionResult> function =
// (exception) => (new handler()).Result((MyException)exception);
// exception =>
var exception = Expression.Parameter(typeof(Exception), "exception");
// new handler()
var newHandler = Expression.New(handlerType);
// (MyException)exception
var cast = Expression.Convert(exception, exceptionType);
// (new handler()).Result((MyException)exception)
var body = Expression.Call(newHandler, handle, cast);
//Func<TException, ActionResult> (exception) =>
// (new handler()).Result((MyException)exception)
var expression = Expression.Lambda(delegateType, body, exception);
return expression;
}
}
Used the following unit test to verify expected behavior
[TestClass]
public class ExceptionHandlerFilterTests {
[TestMethod]
public void Should_Handle_Custom_Exception() {
//Arrange
var subject = new ExceptionHandlerFilter();
var url = "http://example.com";
var context = new ExceptionContext(Mock.Of<ActionContext>(), new List<IFilterMetadata>()) {
Exception = new MovedPermanentlyException(url)
};
//Act
subject.OnException(context);
//Assert
context.Result.Should()
.NotBeNull()
.And.BeOfType<RedirectResult>();
}
}
You may be better off utilizing an if...else or switch statement. Your code could look something like this
private void HandleResponseCodeByExceptionType(ExceptionContext context)
{
var exception = context.Exception;
if (!CanHandle(exception)) return;
var exceptionType = exception.GetType();
if (exceptionType == typeof(MovedPermanantelyException)) {
var handler = new MovePermanentlyExceptionHandler();
handler.Result(exception);
}
else {
// chain the rest of your handlers in else if statements with a default else
}
}
This has the distinct advantage of allowing you to explicitly use the constructors for these handlers instead of attempting to create them with reflection. With reflection you will be unable to add additional parameters to your constructors without a lot of extra work on and modifications to your code.
Background: I have an old class (represented as OriginalComponent in the code below) which I would like to add logging to its methods. The project employs neither an IOC container nor AOP capabilities, and as such I wanted to be cleaver about how I add Logging.
My thought for solution: I feel that a good place to start is the dynamic construct that will act as a decorator class. My code is listed below.
However, my code suffers from a big flaw:
If I do not call the methods in my OriginalComponent class with precise parameter types then the decorator class will not find the methods.
Question:
Does anyone know how I can overcome this shortcoming and ultimately allow me to be able to call a method in a similar inexact way that the compiler allows for an inexact parameter types in a method call.
The following code is written in LinqPad 5.
void Main()
{
var calculator = new OriginalComponent();
var logCalc = new DecoratorLogging<IComponent, LogEnableAttribute>(calculator);
dynamic d = logCalc;
// Does not work
try
{
var ri = d.Add(1, 2);
Console.WriteLine($"Main: ri: {ri.GetType().Name}::{ri}");
Console.WriteLine(new string('-', 20));
Console.WriteLine();
}
catch (Exception ex)
{
Console.WriteLine("ri = d.Add(1, 2) failed");
Console.WriteLine(ex.ExceptionMessages());
Console.WriteLine(new string('=', 60));
Console.WriteLine();
}
// Works...
try
{
var ri = d.Add(1M, 2M);
Console.WriteLine($"Main: ri: {ri.GetType().Name}::{ri}");
Console.WriteLine(new string('-', 20));
Console.WriteLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.ExceptionMessages());
Console.WriteLine(new string('=', 60));
Console.WriteLine();
}
}
// Define other methods and classes here
///// Common interface
public interface IComponent { }
///// OriginalComponent
public class OriginalComponent : IComponent
{
[LogEnable(true)]
public decimal Add(decimal a, decimal b) => a + b;
}
///// DecoratorLogging<TComponent, TAttr>
public class DecoratorLogging<TComponent, TAttr> : DynamicObject, IComponent
where TComponent : class
where TAttr : Attribute, IAttributeEnabled
{
private TComponent _orig;
public DecoratorLogging(TComponent orig = null) { _orig = orig; }
public TComponent Orig => _orig;
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
var methNm = binder.Name;
var parmType = args.Select(a => a.GetType()).ToArray();
MethodInfo methodInfo = null;
dynamic tyObj = this;
for (int i = 0; methodInfo == null; ++i)
{
try { tyObj = tyObj.Orig; }
catch (RuntimeBinderException) { throw new Exception($"Method {methNm} was not found."); }
var ty = tyObj.GetType();
methodInfo = ty.GetMethod(methNm, parmType);
}
result = null;
Stopwatch sw = null;
try
{
BeforeLoggingConcern(binder, args, methodInfo, out sw);
if (tyObj.GetType() == _orig.GetType())
result = methodInfo.Invoke(_orig, args);
else
((dynamic)_orig).TryInvokeMember(binder, args, out result);
AfterLoggingConcern(binder, result, methodInfo, sw);
}
catch (Exception ex)
{
ExceptionLoggingConcern(binder, ex, methodInfo, sw);
throw;
}
return true;
}
private void BeforeLoggingConcern(InvokeMemberBinder binder, object[] args, MethodInfo mi, out Stopwatch sw)
{
sw = null;
var isEnabled = IsLogEnabled(mi);
if (!isEnabled) return;
Console.WriteLine($"Logging Before: {binder.Name} method was called");
var sArgs = string.Join(", ", args.Select(a => $"({a.GetType().Name}, {a})").ToArray());
Console.WriteLine("Logging Aguments: {0}", sArgs);
Console.WriteLine();
sw = new Stopwatch();
sw.Start();
}
private void ExceptionLoggingConcern(InvokeMemberBinder binder, Exception ex, MethodInfo mi, Stopwatch sw)
{
var isEnabled = IsLogEnabled(mi);
if (!isEnabled) return;
if (sw != null)
{
sw.Stop();
Console.WriteLine($"Logging Exception: {binder.Name} threw an exception after {sw.ElapsedMilliseconds} milliseconds");
}
else
Console.WriteLine($"Logging Exception: {binder.Name} threw an exception");
Console.WriteLine($"Logging Internal message:{Environment.NewLine}\t{ex.ExceptionMessages()}");
Console.WriteLine(new string('*', 10));
Console.WriteLine();
}
private void AfterLoggingConcern(InvokeMemberBinder binder, object result, MethodInfo mi, Stopwatch sw)
{
var isEnabled = IsLogEnabled(mi);
if (!isEnabled) return;
if (sw != null)
{
sw.Stop();
Console.WriteLine($"Logging After: {binder.Name} ended after {sw.ElapsedMilliseconds} milliseconds");
}
else
Console.WriteLine($"Logging After: {binder.Name} ended");
Console.WriteLine($"Logging resulting in: type: {result.GetType().Name}, value: {result}");
Console.WriteLine(new string('.', 10));
Console.WriteLine();
}
private bool IsLogEnabled(MethodInfo method)
{
var logAttrs = method.GetCustomAttributes<TAttr>(true);
if (logAttrs == null) return false;
return logAttrs.Any(a => a.IsEnabled);
}
}
///// IAttributeEnabled
public interface IAttributeEnabled
{
bool IsEnabled { get; }
}
///// LogEnableAttribute
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class LogEnableAttribute : Attribute, IAttributeEnabled
{
private bool _logEnabled;
public LogEnableAttribute(bool logEnabled = true) { _logEnabled = logEnabled; }
public bool IsEnabled => _logEnabled;
}
///// Ext (Used to extract all exception messages)
public static class Ext
{
public static string ExceptionMessages(this Exception ex)
{
if (ex.InnerException == null) return ex.Message;
return string.Join($"{Environment.NewLine}\t", ex.InnerExceptions().Select((a, i) => $"{i}. {a.Message}"));
}
public static IEnumerable<Exception> InnerExceptions(this Exception ex)
{
for (Exception ix = ex; ix != null; ix = ix.InnerException)
yield return ix;
}
}
The problem with my code above is attempting to find the method using types that do not exist in the OriginalComponent class. The line:
methodInfo = ty.GetMethod(methNm, parmType);
is the culprit.
What I needed to do is use the Type1.IsAssignableFrom(Type2) to resolve which method to call.
Sometime you need to run specific code on specific threads, for example winforms. To get the code running on the UI thread you need something like this :
this.BeginInvoke(new MethodInvoker(() =>
{
try
{
//code
}
catch(Exception ex)
{
HandleException(ex);
}
}
SynchornixationContext is another way to do the same thing.
Say that we know that we need to run specific code in the UI thread and we have a given way of handling the exceptions that are thrown on this UI thread(BeginInvoke is not blocking so exceptions will not be transfered). How could we create a method that makes the same thing but simplier like this :
RunOnUIThread(MyMethod);
The RunOnUIThread will contains mor or less the same code as the first example in this code.
Is it possible to create a method like this? And if so How?
You can write some nice extension methods like this
public static class ControlExtension
{
public static IAsyncResult BeginInvokeWithExceptionHandling(this Control control, Action method, Action<Exception> exceptionHandler)
{
if (control == null) throw new ArgumentNullException("control");
if (method == null) throw new ArgumentNullException("method");
if (exceptionHandler == null) throw new ArgumentNullException("exceptionHandler");
return control.BeginInvoke(new MethodInvoker(() =>
{
try
{
method();
}
catch (Exception ex)
{
exceptionHandler(ex);
}
}));
}
public static IAsyncResult BeginInvokeWithExceptionHandling<T>(this Control control, Delegate method, Action<Exception> exceptionHandler, params object[] args)
{
if (control == null) throw new ArgumentNullException("control");
if (method == null) throw new ArgumentNullException("method");
if (exceptionHandler == null) throw new ArgumentNullException("exceptionHandler");
return control.BeginInvoke(new MethodInvoker(() =>
{
try
{
method.DynamicInvoke(args);
}
catch (Exception ex)
{
exceptionHandler(ex);
}
}));
}
}
How to use:
private void HandleException(Exception ex)
{
}
private void MyMethod()
{
}
this.BeginInvokeWithExceptionHandling(MyMethod, HandleException);
Note: Due to Delegate.DynamicInvoke this may be little less performing, you can fix it with strong typed delegate. It is also worth noting that control.BeginInvoke is also internally using Delegate.DynamicInvoke if it can't find what the delegate type is.
public static void InvokeControlAction<t>(t cont, Action<t> action) where t : Control
{
if (cont.InvokeRequired)
{ cont.Invoke(new Action<t, Action<t>>(InvokeControlAction),
new object[] { cont, action }); }
else
{ action(cont); }
}
CodeProject Reference
I ended upp with this based on SriramĀ“s suggestion :
public static void SendToUIThread(Action method, bool UseExceptionHandling = true)
{
if (method == null)
throw new ArgumentNullException("method is missing");
_threadSyncContext.Send(new SendOrPostCallback(delegate(object state)
{
if (UseExceptionHandling)
{
try
{
method();
}
catch (Exception ex)
{
ErrorController.Instance.LogAndDisplayException(ex, true);
}
}
else
method();
}), null);
}
public static void PostOnUIThread(this Control control, Action method, bool UseExceptionHandling = true)
{
if (method == null)
throw new ArgumentNullException("method is missing");
if (control.InvokeRequired)
PostOnUIThread(method, UseExceptionHandling);
else
{
if (UseExceptionHandling)
{
try { method(); }
catch (Exception ex) { ErrorController.Instance.LogAndDisplayException(ex, true); }
}
else
method();
}
}
This function will load an assembly, let the user select a form from a list, and then try to invoke it. If successful, returning the form.
My problem is how to instantiate the constructor with parameters that is of the expected type.
if the constructor expect List<string> an empty List<String> should be supplied, not just null.
Any Ideas?
private Form SelectForm(string fileName)
{
Assembly assembly = Assembly.LoadFrom(fileName);
var asmTypes = assembly.GetTypes().Where(F => F.IsSubclassOf(typeof(Form)));
string SelectedFormName;
using (FrmSelectForm form = new FrmSelectForm())
{
form.DataSource = (from row in asmTypes
select new { row.Name, row.Namespace, row.BaseType }).ToList();
if (form.ShowDialog(this) != DialogResult.OK)
return null;
SelectedFormName = form.SelectedForm;
}
Type t = asmTypes.Single<Type>(F => F.Name == SelectedFormName);
foreach (var ctor in t.GetConstructors())
{
try
{
object[] parameters = new object[ctor.GetParameters().Length];
for (int i = 0; i < ctor.GetParameters().Length; i++)
{
parameters[i] = ctor.GetParameters()[i].DefaultValue;
}
return Activator.CreateInstance(t, parameters) as Form;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
return null;
}
If you know what is a parameter type, replace:
parameters[i] = ctor.GetParameters()[i].DefaultValue;
to
parameters[i] = new List<string>();
If you don't know, you need create instance using same reflection methods:
object p1 = Activator.CreateInstance(parameters[i].ParameterType),
return Activator.CreateInstance(t, [p1]) as Form;
in order to create objects from types definitions this method works very well.
private Form SelectForm(string fileName,string formName)
{
Assembly assembly = Assembly.LoadFrom(fileName);
var asmTypes = assembly.GetTypes().Where(F => F.IsSubclassOf(typeof(Form)));
Type t = asmTypes.Single<Type>(F => F.Name == formName);
try
{
var ctor = t.GetConstructors()[0];
List<object> parameters = new List<object>();
foreach (var param in ctor.GetParameters())
{
parameters.Add(GetNewObject(param.ParameterType));
}
return ctor.Invoke(parameters.ToArray()) as Form;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return null;
}
...
public static object GetNewObject(Type t)
{
try
{
return t.GetConstructor(new Type[] { }).Invoke(new object[] { });
}
catch
{
return null;
}
}
I'm trying to expose a model to be available for OData services. The approach I'm currently taking is along the lines of:
1) Defining a class in the model to expose IQueryable collections such as:
public class MyEntities
{
public IQueryable<Customer> Customers
{
get
{
return DataManager.GetCustomers().AsQueryable<Customer>();
}
}
public IQueryable<User> Users
{
get
{
return DataManager.GetUsers().AsQueryable<User>();
}
}
}
2) Set up a WCF DataService with the queryable collection class such as:
public class MyDataService : DataService<MyEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
config.SetEntitySetAccessRule("Users", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
I'm running into 3 issues and/or limitations with this approach:
1) I'm unable to add any derived class collections to the IQueryable lists.
2) I must apply the IgnoreProperties attribute to hide any members that are derived from a base type.
3) I'm unable to prevent unwanted entities from being accessed by the OData service and causing errors. For example, I only want BLL layer objects to be exposed, but it seems like the model is being reflected far beyond the members of the classes I added to the queryable list, and picking up all the DAL classes, causing errors being undefined and also having the same name as the BLL classes. There are no links to DAL classes from BLL class members. At the very least, I would like to have these classes ignored altogether.
Any pointers on how to address any of these issues would be greatly appreciated. Should I be doing a different approach on this? For example, should I implement IQueryable directly in my model collections?
Thanks.
The reflection provider which you're using is designed to walk all public types/properties. So the #3 and probably even #2 (which I don't fully understand what's the problem) are by design because of that.
#1 is also by design but for a different reason - the reflection provider can only expose one entity set for each type hierarchy. It doesn't support so called "MEST" (Multiple Entity Sets per Type), because it would not know which one to pick. It needs a 1 to 1 mapping between entity types and entity sets.
The reflection provider is meant for simple services which are "Easy" to setup. It's definitely not designed for customizations.
If you want greater control, then you need custom provider, which can be either implemented directly (if it's based on existing CLR classes it's not that hard), or through some library, like the one suggested in the comments above.
The reflection provider is not designed to handle rich data models with a fair amount of inheritance and other dependences. I ended up building a custom provider that could handle queries, updates, inheritance, and relationships based on Alex James' excellent blog post on Creating a Data Service Provider.
An example implementation with 3 CLR classes: ResidentialCustomer, Customer, and User is provided below. ResidentialCustomer extends Customer, Customer has a list of Users, and User has a reference back to Customer.
An interface for DataContext classes such as:
public interface IODataContext
{
IQueryable GetQueryable(ResourceSet set);
object CreateResource(ResourceType resourceType);
void AddResource(ResourceType resourceType, object resource);
void DeleteResource(object resource);
void SaveChanges();
}
A class to implement IDataServiceMetadataProvider such as:
public class ODataServiceMetadataProvider : IDataServiceMetadataProvider
{
private Dictionary<string, ResourceType> resourceTypes = new Dictionary<string, ResourceType>();
private Dictionary<string, ResourceSet> resourceSets = new Dictionary<string, ResourceSet>();
private List<ResourceAssociationSet> _associationSets = new List<ResourceAssociationSet>();
public string ContainerName
{
get { return "MyDataContext"; }
}
public string ContainerNamespace
{
get { return "MyNamespace"; }
}
public IEnumerable<ResourceSet> ResourceSets
{
get { return this.resourceSets.Values; }
}
public IEnumerable<ServiceOperation> ServiceOperations
{
get { yield break; }
}
public IEnumerable<ResourceType> Types
{
get { return this.resourceTypes.Values; }
}
public bool TryResolveResourceSet(string name, out ResourceSet resourceSet)
{
return resourceSets.TryGetValue(name, out resourceSet);
}
public bool TryResolveResourceType(string name, out ResourceType resourceType)
{
return resourceTypes.TryGetValue(name, out resourceType);
}
public bool TryResolveServiceOperation(string name, out ServiceOperation serviceOperation)
{
serviceOperation = null;
return false;
}
public void AddResourceType(ResourceType type)
{
type.SetReadOnly();
resourceTypes.Add(type.FullName, type);
}
public void AddResourceSet(ResourceSet set)
{
set.SetReadOnly();
resourceSets.Add(set.Name, set);
}
public bool HasDerivedTypes(ResourceType resourceType)
{
if (resourceType.InstanceType == typeof(ResidentialCustomer))
{
return true;
}
return false;
}
public IEnumerable<ResourceType> GetDerivedTypes(ResourceType resourceType)
{
List<ResourceType> derivedResourceTypes = new List<ResourceType>();
if (resourceType.InstanceType == typeof(ResidentialCustomer))
{
foreach (ResourceType resource in Types)
{
if (resource.InstanceType == typeof(Customer))
{
derivedResourceTypes.Add(resource);
}
}
}
return derivedResourceTypes;
}
public void AddAssociationSet(ResourceAssociationSet associationSet)
{
_associationSets.Add(associationSet);
}
public ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType, ResourceProperty resourceProperty)
{
return resourceProperty.CustomState as ResourceAssociationSet;
}
public ODataServiceMetadataProvider() { }
}
A class to implement IDataServiceQueryProvider such as:
public class ODataServiceQueryProvider<T> : IDataServiceQueryProvider where T : IODataContext
{
T _currentDataSource;
IDataServiceMetadataProvider _metadata;
public object CurrentDataSource
{
get
{
return _currentDataSource;
}
set
{
_currentDataSource = (T)value;
}
}
public bool IsNullPropagationRequired
{
get { return true; }
}
public object GetOpenPropertyValue(object target, string propertyName)
{
throw new NotImplementedException();
}
public IEnumerable<KeyValuePair<string, object>> GetOpenPropertyValues(object target)
{
throw new NotImplementedException();
}
public object GetPropertyValue(object target, ResourceProperty resourceProperty)
{
throw new NotImplementedException();
}
public IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet)
{
return _currentDataSource.GetQueryable(resourceSet);
}
public ResourceType GetResourceType(object target)
{
Type type = target.GetType();
return _metadata.Types.Single(t => t.InstanceType == type);
}
public object InvokeServiceOperation(ServiceOperation serviceOperation, object[] parameters)
{
throw new NotImplementedException();
}
public ODataServiceQueryProvider(IDataServiceMetadataProvider metadata)
{
_metadata = metadata;
}
}
A class to implement IDataServiceUpdateProvider such as:
public class ODataServiceUpdateProvider<T> : IDataServiceUpdateProvider where T : IODataContext
{
private IDataServiceMetadataProvider _metadata;
private ODataServiceQueryProvider<T> _query;
private List<Action> _actions;
public T GetContext()
{
return ((T)_query.CurrentDataSource);
}
public void SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues)
{
throw new NotImplementedException();
}
public void SetReference(object targetResource, string propertyName, object propertyValue)
{
_actions.Add(() => ReallySetReference(targetResource, propertyName, propertyValue));
}
public void ReallySetReference(object targetResource, string propertyName, object propertyValue)
{
targetResource.SetPropertyValue(propertyName, propertyValue);
}
public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
{
_actions.Add(() => ReallyAddReferenceToCollection(targetResource, propertyName, resourceToBeAdded));
}
public void ReallyAddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
{
var collection = targetResource.GetPropertyValue(propertyName);
if (collection is IList)
{
(collection as IList).Add(resourceToBeAdded);
}
}
public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
{
_actions.Add(() => ReallyRemoveReferenceFromCollection(targetResource, propertyName, resourceToBeRemoved));
}
public void ReallyRemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
{
var collection = targetResource.GetPropertyValue(propertyName);
if (collection is IList)
{
(collection as IList).Remove(resourceToBeRemoved);
}
}
public void ClearChanges()
{
_actions.Clear();
}
public void SaveChanges()
{
foreach (var a in _actions)
a();
GetContext().SaveChanges();
}
public object CreateResource(string containerName, string fullTypeName)
{
ResourceType type = null;
if (_metadata.TryResolveResourceType(fullTypeName, out type))
{
var context = GetContext();
var resource = context.CreateResource(type);
_actions.Add(() => context.AddResource(type, resource));
return resource;
}
throw new Exception(string.Format("Type {0} not found", fullTypeName));
}
public void DeleteResource(object targetResource)
{
_actions.Add(() => GetContext().DeleteResource(targetResource));
}
public object GetResource(IQueryable query, string fullTypeName)
{
var enumerator = query.GetEnumerator();
if (!enumerator.MoveNext())
throw new Exception("Resource not found");
var resource = enumerator.Current;
if (enumerator.MoveNext())
throw new Exception("Resource not uniquely identified");
if (fullTypeName != null)
{
ResourceType type = null;
if (!_metadata.TryResolveResourceType(fullTypeName, out type))
throw new Exception("ResourceType not found");
if (!type.InstanceType.IsAssignableFrom(resource.GetType()))
throw new Exception("Unexpected resource type");
}
return resource;
}
public object ResetResource(object resource)
{
_actions.Add(() => ReallyResetResource(resource));
return resource;
}
public void ReallyResetResource(object resource)
{
var clrType = resource.GetType();
ResourceType resourceType = _metadata.Types.Single(t => t.InstanceType == clrType);
var resetTemplate = GetContext().CreateResource(resourceType);
foreach (var prop in resourceType.Properties
.Where(p => (p.Kind & ResourcePropertyKind.Key) != ResourcePropertyKind.Key))
{
var clrProp = clrType.GetProperties().Single(p => p.Name == prop.Name);
var defaultPropValue = clrProp.GetGetMethod().Invoke(resetTemplate, new object[] { });
clrProp.GetSetMethod().Invoke(resource, new object[] { defaultPropValue });
}
}
public object ResolveResource(object resource)
{
return resource;
}
public object GetValue(object targetResource, string propertyName)
{
var value = targetResource.GetType().GetProperties().Single(p => p.Name == propertyName).GetGetMethod().Invoke(targetResource, new object[] { });
return value;
}
public void SetValue(object targetResource, string propertyName, object propertyValue)
{
targetResource.GetType().GetProperties().Single(p => p.Name == propertyName).GetSetMethod().Invoke(targetResource, new[] { propertyValue });
}
public ODataServiceUpdateProvider(IDataServiceMetadataProvider metadata, ODataServiceQueryProvider<T> query)
{
_metadata = metadata;
_query = query;
_actions = new List<Action>();
}
}
A class to implement IServiceProvider such as:
public class ODataService<T> : DataService<T>, IServiceProvider where T : IODataContext
{
private ODataServiceMetadataProvider _metadata;
private ODataServiceQueryProvider<T> _query;
private ODataServiceUpdateProvider<T> _updater;
public object GetService(Type serviceType)
{
if (serviceType == typeof(IDataServiceMetadataProvider))
{
return _metadata;
}
else if (serviceType == typeof(IDataServiceQueryProvider))
{
return _query;
}
else if (serviceType == typeof(IDataServiceUpdateProvider))
{
return _updater;
}
else
{
return null;
}
}
public ODataServiceMetadataProvider GetMetadataProvider(Type dataSourceType)
{
ODataServiceMetadataProvider metadata = new ODataServiceMetadataProvider();
ResourceType customer = new ResourceType(
typeof(Customer),
ResourceTypeKind.EntityType,
null,
"MyNamespace",
"Customer",
false
);
ResourceProperty customerCustomerID = new ResourceProperty(
"CustomerID",
ResourcePropertyKind.Key |
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(Guid))
);
customer.AddProperty(customerCustomerID);
ResourceProperty customerCustomerName = new ResourceProperty(
"CustomerName",
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(string))
);
customer.AddProperty(customerCustomerName);
ResourceType residentialCustomer = new ResourceType(
typeof(ResidentialCustomer),
ResourceTypeKind.EntityType,
customer,
"MyNamespace",
"ResidentialCustomer",
false
);
ResourceType user = new ResourceType(
typeof(User),
ResourceTypeKind.EntityType,
null,
"MyNamespace",
"User",
false
);
ResourceProperty userUserID = new ResourceProperty(
"UserID",
ResourcePropertyKind.Key |
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(Guid))
);
user.AddProperty(userUserID);
ResourceProperty userCustomerID = new ResourceProperty(
"CustomerID",
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(Guid))
);
user.AddProperty(userCustomerID);
ResourceProperty userEmailAddress = new ResourceProperty(
"EmailAddress",
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(string))
);
user.AddProperty(userEmailAddress);
var customerSet = new ResourceSet("Customers", customer);
var residentialCustomerSet = new ResourceSet("ResidentialCustomers", residentialCustomer);
var userSet = new ResourceSet("Users", user);
var userCustomer = new ResourceProperty(
"Customer",
ResourcePropertyKind.ResourceReference,
customer
);
user.AddProperty(userCustomer);
var customerUserList = new ResourceProperty(
"UserList",
ResourcePropertyKind.ResourceSetReference,
user
);
customer.AddProperty(customerUserList);
metadata.AddResourceType(customer);
metadata.AddResourceSet(customerSet);
metadata.AddResourceType(residentialCustomer);
metadata.AddResourceSet(residentialCustomerSet);
metadata.AddResourceType(user);
metadata.AddResourceSet(userSet);
ResourceAssociationSet customerUserListSet = new ResourceAssociationSet(
"CustomerUserList",
new ResourceAssociationSetEnd(
customerSet,
customer,
customerUserList
),
new ResourceAssociationSetEnd(
userSet,
user,
userCustomer
)
);
customerUserList.CustomState = customerUserListSet;
userCustomer.CustomState = customerUserListSet;
metadata.AddAssociationSet(customerUserListSet);
return metadata;
}
public ODataServiceQueryProvider<T> GetQueryProvider(ODataServiceMetadataProvider metadata)
{
return new ODataServiceQueryProvider<T>(metadata);
}
public ODataServiceUpdateProvider<T> GetUpdateProvider(ODataServiceMetadataProvider metadata, ODataServiceQueryProvider<T> query)
{
return new ODataServiceUpdateProvider<T>(metadata, query);
}
public ODataService()
{
_metadata = GetMetadataProvider(typeof(T));
_query = GetQueryProvider(_metadata);
_updater = GetUpdateProvider(_metadata, _query);
}
}
The DataContext class holds the CLR collections and wires up the service operations such as:
public partial class MyDataContext: IODataContext
{
private List<Customer> _customers = null;
public List<Customer> Customers
{
get
{
if (_customers == null)
{
_customers = DataManager.GetCustomers);
}
return _customers;
}
}
private List<ResidentialCustomer> _residentialCustomers = null;
public List<ResidentialCustomer> ResidentialCustomers
{
get
{
if (_residentialCustomers == null)
{
_residentialCustomers = DataManager.GetResidentialCustomers();
}
return _residentialCustomers;
}
}
private List<User> _users = null;
public List<User> Users
{
get
{
if (_users == null)
{
_users = DataManager.GetUsers();
}
return _users;
}
}
public IQueryable GetQueryable(ResourceSet set)
{
if (set.Name == "Customers") return Customers.AsQueryable();
if (set.Name == "ResidentialCustomers") return ResidentialCustomers.AsQueryable();
if (set.Name == "Users") return Users.AsQueryable();
throw new NotSupportedException(string.Format("{0} not found", set.Name));
}
public object CreateResource(ResourceType resourceType)
{
if (resourceType.InstanceType == typeof(Customer))
{
return new Customer();
}
if (resourceType.InstanceType == typeof(ResidentialCustomer))
{
return new ResidentialCustomer();
}
if (resourceType.InstanceType == typeof(User))
{
return new User();
}
throw new NotSupportedException(string.Format("{0} not found for creating.", resourceType.FullName));
}
public void AddResource(ResourceType resourceType, object resource)
{
if (resourceType.InstanceType == typeof(Customer))
{
Customer i = resource as Customer;
if (i != null)
{
Customers.Add(i);
return;
}
}
if (resourceType.InstanceType == typeof(ResidentialCustomer))
{
ResidentialCustomeri = resource as ResidentialCustomer;
if (i != null)
{
ResidentialCustomers.Add(i);
return;
}
}
if (resourceType.InstanceType == typeof(User))
{
Useri = resource as User;
if (i != null)
{
Users.Add(i);
return;
}
}
throw new NotSupportedException(string.Format("{0} not found for adding.", resourceType.FullName));
}
public void DeleteResource(object resource)
{
if (resource.GetType() == typeof(Customer))
{
Customers.Remove(resource as Customer);
return;
}
if (resource.GetType() == typeof(ResidentialCustomer))
{
ResidentialCustomers.Remove(resource as ResidentialCustomer);
return;
}
if (resource.GetType() == typeof(User))
{
Users.Remove(resource as User);
return;
}
throw new NotSupportedException(string.Format("{0} not found for deletion.", resource.GetType().FullName));
}
public void SaveChanges()
{
foreach (var item in Customers.Where(i => i.IsModified == true))
item.Save();
foreach (var item in ResidentialCustomers.Where(i => i.IsModified == true))
item.Save();
foreach (var item in Users.Where(i => i.IsModified == true))
item.Save();
}
}
Then, create your data service using the custom data service class and your data context, such as:
public class MyDataService : ODataService<MyDataContext>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
config.SetEntitySetAccessRule("ResidentialCustomers", EntitySetRights.All);
config.SetEntitySetAccessRule("Users", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
config.DataServiceBehavior.AcceptProjectionRequests = true;
}
}
Lots of wiring up, but pretty straightforward once you've got the hang of it.