OData Client Request Pipeline not working in Odata V4 - odata

I am following the sample blog below to remove and add properties in request ODataEntry class.
http://blogs.msdn.com/b/odatateam/archive/2013/07/26/using-the-new-client-hooks-in-wcf-data-services-client.aspx
But even if the code works fine and adds and removes the properties correctly when I put breakpoint, all the entity properties goes to server un changed.
Only difference I see this I am using the OData V4 and new Ondata client to hook up.
My code looks below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Client.Default;
namespace Client
{
using Client.MvcApplication1.Models;
using Microsoft.OData.Core;
internal class Program
{
private static void Main(string[] args)
{
Container container = new Container(new Uri("http://localhost:55000/api/"));
container.Configurations.RequestPipeline.OnEntryEnding(
w =>
{
w.Entry.RemoveProperties("Name");
});
Test test = new Test();
test.Name = "Foo";
CustomFields cs = new CustomFields { ServiceId = 3 };
cs.Foo1 = 2;
test.S_1 = cs;
container.AddToTests(test);
container.SaveChanges();
}
}
public static class Extensions
{
public static void RemoveProperties(this ODataEntry entry, params string[] propertyNames)
{
var properties = entry.Properties as List<ODataProperty>;
if (properties == null)
{
properties = new List<ODataProperty>(entry.Properties);
}
var propertiesToRemove = properties.Where(p => propertyNames.Any(rp => rp == p.Name));
foreach (var propertyToRemove in propertiesToRemove.ToArray())
{
properties.Remove(propertyToRemove);
}
entry.Properties = properties;
}
public static void AddProperties(this ODataEntry entry, params ODataProperty[] newProperties)
{
var properties = entry.Properties as List<ODataProperty>;
if (properties == null)
{
properties = new List<ODataProperty>(entry.Properties);
}
properties.AddRange(newProperties);
entry.Properties = properties;
}
}
}
If I change and start listening to RequestPipeline.OnEntryStarting I get the validation error that new property is not defined in owning entity. But as per code for Microsoft.OData.CLient this error should not occure as there is a check for IEdmStructuredType.IsOpen but still error occurs. So issue seems deep in how owningStructuredType is calculated. On my container I do see the correct edm model with entities marked as IsOpen = true.
Odata lib code which should pass but is failing
internal static IEdmProperty ValidatePropertyDefined(string propertyName, IEdmStructuredType owningStructuredType)
{
Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)");
if (owningStructuredType == null)
{
return null;
}
IEdmProperty property = owningStructuredType.FindProperty(propertyName);
// verify that the property is declared if the type is not an open type.
if (!owningStructuredType.IsOpen && property == null)
{
throw new ODataException(Strings.ValidationUtils_PropertyDoesNotExistOnType(propertyName, owningStructuredType.ODataFullName()));
}
return property;
}
Client code:
container.Configurations.RequestPipeline.OnEntryStarting(
w =>
{
w.Entry.RemoveProperties("Name");
w.Entry.AddProperties(new ODataProperty
{
Name = "NewProperty",
Value = 1
});
});
Error:
The property 'NewProperty' does not exist on type 'Client.MvcApplication1.Models.Test'. Make sure to only use property names that are defined by the type.
at Microsoft.OData.Core.WriterValidationUtils.ValidatePropertyDefined(String propertyName, IEdmStructuredType owningStructuredType)
at Microsoft.OData.Core.JsonLight.ODataJsonLightPropertySerializer.WriteProperty(ODataProperty property, IEdmStructuredType owningType, Boolean isTopLevel, Boolean allowStreamProperty, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties)
at Microsoft.OData.Core.JsonLight.ODataJsonLightPropertySerializer.WriteProperties(IEdmStructuredType owningType, IEnumerable`1 properties, Boolean isComplexValue, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties)
at Microsoft.OData.Core.JsonLight.ODataJsonLightWriter.StartEntry(ODataEntry entry)
at Microsoft.OData.Core.ODataWriterCore.<>c__DisplayClass14.<WriteStartEntryImplementation>b__12()
at Microsoft.OData.Core.ODataWriterCore.InterceptException(Action action)
at Microsoft.OData.Core.ODataWriterCore.WriteStartEntryImplementation(ODataEntry entry)
at Microsoft.OData.Core.ODataWriterCore.WriteStart(ODataEntry entry)
at Microsoft.OData.Client.ODataWriterWrapper.WriteStart(ODataEntry entry, Object entity)
at Microsoft.OData.Client.Serializer.WriteEntry(EntityDescriptor entityDescriptor, IEnumerable`1 relatedLinks, ODataRequestMessageWrapper requestMessage)
at Microsoft.OData.Client.BaseSaveResult.CreateRequestData(EntityDescriptor entityDescriptor, ODataRequestMessageWrapper requestMessage)
at Microsoft.OData.Client.BaseSaveResult.CreateChangeData(Int32 index, ODataRequestMessageWrapper requestMessage)
at Microsoft.OData.Client.SaveResult.CreateNonBatchChangeData(Int32 index, ODataRequestMessageWrapper requestMessage)
at Microsoft.OData.Client.SaveResult.CreateNextChange()

I use partial classes defined on the client to add the extra properties that I need there. This allows me to put any logic in them as well as have property changed notification as well. I the use the following extension methods to remove those properties. I think I actually got the original code from the article that you linked.
public static class DbContextExtensions
{
public static void RemoveProperties(this ODataEntry entry, params string[] propertyNames)
{
var properties = entry.Properties as List<ODataProperty>;
if (properties == null)
{
properties = new List<ODataProperty>(entry.Properties);
}
var propertiesToRemove = properties.Where(p => propertyNames.Any(rp => rp == p.Name));
foreach (var propertyToRemove in propertiesToRemove.ToArray())
{
properties.Remove(propertyToRemove);
}
entry.Properties = properties;
}
public static DataServiceClientResponsePipelineConfiguration RemoveProperties<T>(this DataServiceClientResponsePipelineConfiguration responsePipeline, Func<string, Type> resolveType, params string[] propertiesToRemove)
{
return responsePipeline.OnEntryEnded((args) =>
{
Type resolvedType = resolveType(args.Entry.TypeName);
if (resolvedType != null && typeof(T).IsAssignableFrom(resolvedType))
{
args.Entry.RemoveProperties(propertiesToRemove);
}
});
}
public static DataServiceClientRequestPipelineConfiguration RemoveProperties<T>(this DataServiceClientRequestPipelineConfiguration requestPipeline, params string[] propertiesToRemove)
{
return requestPipeline.OnEntryStarting((args) =>
{
if (typeof(T).IsAssignableFrom(args.Entity.GetType()))
{
args.Entry.RemoveProperties(propertiesToRemove);
}
});
}
}
Notice that in the method below it is hooking OnEntryStarted. The code in the article hooks OnEntryEnded which worked for me at one point and then broke when I updated to a newer version of ODataClient. OnEntryStarted is the way to go in this method.
public static DataServiceClientRequestPipelineConfiguration RemoveProperties<T>(this DataServiceClientRequestPipelineConfiguration requestPipeline, params string[] propertiesToRemove)
{
return requestPipeline.OnEntryStarting((args) =>
{
if (typeof(T).IsAssignableFrom(args.Entity.GetType()))
{
args.Entry.RemoveProperties(propertiesToRemove);
}
});
}
I also created a partial class for the Container as well and implement the partial method OnContextCreated. This is where you use the extension methods to remove the properties that won't get sent to the server.
partial void OnContextCreated()
{
Configurations.RequestPipeline.RemoveProperties<Customer>(new string[] { "FullName", "VersionDetails" });
Configurations.RequestPipeline.RemoveProperties<SomeOtherType>(new string[] { "IsChecked", "IsReady" });
}
Make sure that your partial classes and the DBContextExtensions class are in the same namespace as our container and everything should just work.
Hope that helps.

Related

Sitecore Microsoft Dependency Injection error "Service has been disposed, cannot create object"

I am using Sitecore 8.2 Update 4 with Helix framework also using Microsoft Extension Dependency Injection. I have performed few steps for DI:
1. Created DI project in Foundation layer.
2. I have created on pipeline with name Habitat.Foundation.DI.RegisterControllers inside
App_config/Include/zFoundation
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"><sitecore> <services> <configurator type=" Habitat.Foundation.DI.RegisterControllers, Habitat.Foundation.DI" /></services></sitecore></configuration>
namespace Habitat.Foundation.DI{ public class RegisterControllers : IServicesConfigurator
{
public void Configure(IServiceCollection serviceCollection)
{
serviceCollection.AddMvcControllers("*.Feature.*");
}
}}
namespace Habitat.Foundation.DI.Extensions{
public static class ServiceCollectionExtensions
{
public static void AddMvcControllers(this IServiceCollection serviceCollection, params string[] assemblyFilters)
{
var assemblies = GetAssemblies.GetByFilter(assemblyFilters);
AddMvcControllers(serviceCollection, assemblies);
}
public static void AddMvcControllers(this IServiceCollection serviceCollection, params Assembly[] assemblies)
{
var controllers = GetTypes.GetTypesImplementing<IController>(assemblies)
.Where(controller => controller.Name.EndsWith("Controller", StringComparison.Ordinal));
foreach (var controller in controllers)
serviceCollection.AddTransient(controller);
controllers = GetTypes.GetTypesImplementing<ApiController>(assemblies)
.Where(controller => controller.Name.EndsWith("Controller", StringComparison.Ordinal));
foreach (var controller in controllers)
serviceCollection.AddTransient(controller);
}
}}
public static class GetAssemblies
{
public static Assembly[] GetByFilter(params string[] assemblyFilters)
{
var assemblyNames = new HashSet<string>(assemblyFilters.Where(filter => !filter.Contains('*')));
var wildcardNames = assemblyFilters.Where(filter => filter.Contains('*')).ToArray();
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(assembly =>
{
var nameToMatch = assembly.GetName().Name;
if (assemblyNames.Contains(nameToMatch)) return true;
return wildcardNames.Any(wildcard => IsWildcardMatch(nameToMatch, wildcard));
})
.ToArray();
return assemblies;
}
/// <summary>
/// Checks if a string matches a wildcard argument (using regex)
/// </summary>
private static bool IsWildcardMatch(string input, string wildcards)
{
return Regex.IsMatch(input, "^" + Regex.Escape(wildcards).Replace("\\*", ".*").Replace("\\?", ".") + "$",
RegexOptions.IgnoreCase);
}
}public static class GetTypes {
public static Type[] GetTypesImplementing<T>(params Assembly[] assemblies)
{
if (assemblies == null || assemblies.Length == 0)
return new Type[0];
var targetType = typeof(T);
return assemblies
.Where(assembly => !assembly.IsDynamic)
.SelectMany(GetExportedTypes)
.Where(type => !type.IsAbstract && !type.IsGenericTypeDefinition && targetType.IsAssignableFrom(type))
.ToArray();
}
private static IEnumerable<Type> GetExportedTypes(Assembly assembly)
{
try
{
return assembly.GetExportedTypes();
}
catch (NotSupportedException)
{
// A type load exception would typically happen on an Anonymously Hosted DynamicMethods
// Assembly and it would be safe to skip this exception.
return Type.EmptyTypes;
}
catch (ReflectionTypeLoadException ex)
{
// Return the types that could be loaded. Types can contain null values.
return ex.Types.Where(type => type != null);
}
catch (Exception ex)
{
// Throw a more descriptive message containing the name of the assembly.
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Unable to load types from assembly {0}. {1}", assembly.FullName, ex.Message), ex);
}
}
}
As I am using Glass Mapper so I want to use ISitecoreContext, for that I have register ISitecoreContext in Foundation/ORM for that I have performed these actions:
1. Created patch file inside Foundation/ORM
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"><sitecore><services>
<configurator type="Habitat.Foundation.ORM.DI.RegisterContainer, Habitat.Foundation.ORM" />
</services> </sitecore>
public class RegisterContainer : IServicesConfigurator
{
public void Configure(IServiceCollection serviceCollection)
{
serviceCollection.AddTransient<ISitecoreContext, SitecoreContext>();
}
}
Accesssing in controller like this
private readonly ISitecoreContext _sitecoreContext;
public TestController(ISitecoreContext sitecoreContext)
{
_sitecoreContext = sitecoreContext;
}
I accessing in two classes one way is _sitecoreContext.GetItem(Context.Site.ContentStartPath) and another way is _sitecoreContext.GetRootItem() then it started throwing error “{"Service has been disposed, cannot create object"}” in _sitecoreContext.GetItem(Context.Site.ContentStartPath) but working for
_sitecoreContext.GetRootItem()
When I update AddTransient to AddSingleton then it works for some time for both but for after some time it started giving me null value of _sitecoreContext.
Please help me.

Resolve named Dependencies with Dependency Resolver

I've noticed that when I register my dependencies via named overrides Dependency Resolver struggles to resolve components properly. Seems like the first instance is provided. Everything is fine with ctor injection.
Example:
Registration
RegisterProvider<IAccountProvider, AccountProvider>();
RegisterProvider<IAccountProvider, CustomAccountProvider>("customAccountProvider");
Resolution
var instance = DependecyResolver.Current.GetService<IAccountProvider>();
Cannot retrieve customAccountProvider instance.
It always refers to the first registered component ignoring named constraints.
When you have multiple implementations of the same component you have to name them or mark them with marking interface. Here is a code example with naming the instances :
using System;
using System.Linq;
using System.Reflection;
using Castle.Facilities.TypedFactory;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
namespace ResolvingNamedInctances
{
class Program
{
static void Main(string[] args)
{
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<ITypedFactoryComponentSelector>().ImplementedBy<AccountProviderTypeSelector>());
container.Register(Component.For<IAccountProviderSelector>().AsFactory(typeof(AccountProviderTypeSelector)));
container.Register(Component.For<IAccountProvider>().ImplementedBy<DefaultAccountProvider>().Named("default"));
container.Register(Component.For<IAccountProvider>().ImplementedBy<CustomAccountProvider>().Named("custom"));
// uncomment this line in MVC app and use DependencyResolver instead of container
//DependencyResolver.SetResolver(new WindsorDependencyResolver(container.Kernel));
var accountProviderSelector = container.Resolve<IAccountProviderSelector>();
var defaultAccountProvider = accountProviderSelector.GetAccountProvider(); // default
defaultAccountProvider.Provide();
var customAccountProvider = accountProviderSelector.GetAccountProvider("custom");
customAccountProvider.Provide();
Console.ReadLine();
}
}
public class AccountProviderTypeSelector : ITypedFactoryComponentSelector
{
public Func<IKernelInternal, IReleasePolicy, object> SelectComponent(MethodInfo method, Type type, object[] arguments)
{
string providerName = arguments.Length > 0 ? (string)arguments[0] : "default";
return (k, r) => k.GetHandlers(typeof(IAccountProvider))
.Where(
h =>
{
return h.ComponentModel.Name == providerName;
})
.Select(h => (IAccountProvider)k.Resolve(
h.ComponentModel.Name,
typeof(IAccountProvider),
new Arguments { },
r))
.FirstOrDefault();
}
}
public interface IAccountProviderSelector
{
IAccountProvider GetAccountProvider();
IAccountProvider GetAccountProvider(string nameIdentifier);
}
public interface IAccountProvider
{
void Provide();
}
public class DefaultAccountProvider : IAccountProvider
{
public void Provide()
{
Console.WriteLine("Working in default AccountProvider");
}
}
public class CustomAccountProvider : IAccountProvider
{
public void Provide()
{
Console.WriteLine("Working in standart CustomAccountProvider");
}
}
}

How to test whether a lazy loaded child collection has been loaded

Suppose I have users with tags being lazy loaded:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
If I get a user this way, I know that the tags aren't loaded:
User myUser;
using (var context = new MyContext())
{
myUser = context.Users.Find(4);
}
How do I test the Tags collection presence outside of the using clause?
if (myUser.Tags == null) // throws an ObjectDisposedException
I could use a try/catch but there must be a better way.
The only way I can think of is to be able to do a non virtual call (similar to when you do base.Something in a derived class) to the class property getter. Since there is no way to do that with pure C# or reflection, I've ended up with the following helper method which utilizes System.Reflection.Emit LCG (lightweight code generation) to emit Call IL instruction instead of the normal Callvirt:
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
public static class Utils
{
public static TValue GetClassValue<TSource, TValue>(this TSource source, Expression<Func<TSource, TValue>> selector)
where TSource : class
{
Func<TSource, TValue> getValue = null;
if (source.GetType() != typeof(TSource))
{
var propertyAccessor = selector.Body as MemberExpression;
if (propertyAccessor != null)
{
var propertyInfo = propertyAccessor.Member as PropertyInfo;
if (propertyInfo != null)
{
var getMethod = propertyInfo.GetGetMethod();
if (getMethod != null && getMethod.IsVirtual)
{
var dynamicMethod = new DynamicMethod("", typeof(TValue), new[] { typeof(TSource) }, typeof(Utils), true);
var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Call, getMethod, null);
il.Emit(OpCodes.Ret);
getValue = (Func<TSource, TValue>)dynamicMethod.CreateDelegate(typeof(Func<TSource, TValue>));
}
}
}
}
if (getValue == null)
getValue = selector.Compile();
return getValue(source);
}
}
It can be used for both single and collection type navigation properties like this:
if (myUser.GetClassValue(x => x.Tags) == null)
Another solution using a naive try/catch, not sure about the performance compared to the other answer:
using System;
public static class EntityFrameworkExtensions
{
public static bool IsCollectionLoaded<TSource, TValue>(this TSource source, Func<TSource, TValue> selector)
where TSource : class
{
try
{
return (selector(source) != null);
}
catch (ObjectDisposedException)
{
return false;
}
}
}
usage:
if (myUser.IsCollectionLoaded(x => x.Tags))

Web API, Light Inject and Passing a Static Dictionary to the data layer

We have a multi-database solution and are passing the connection string to a factory function like so:
container.Register<IDbContextFactory>(
f => new DynamicDbContextFactory(ClientConfig.GetConnectionString()),
new PerScopeLifetime());
ClientConfig contains a static dictionary that gets populated on app start that maps a sub domain to a connection string. It seems that this approach is causing a memory leak (not 100% sure about this causing the leak but there is a leak).
public class ClientConfig
{
private static ConcurrentDictionary<string, string> ConnectionStringManager
{
get;
set;
}
// etc.
}
My question is in MVC what is the best way to hold a list of connection strings that can be easily looked up on each request in order to pass that down the chain.
Edit : The question was initially tagged with Autofac
With Autofac you don't have to use a dictionary and something like that to do what you want. You can use a custom parameter :
public class ConnectionStringParameter : Parameter
{
public override Boolean CanSupplyValue(ParameterInfo pi,
IComponentContext context,
out Func<Object> valueProvider)
{
valueProvider = null;
if (pi.ParameterType == typeof(String)
&& String.Equals(pi.Name, "connectionString",
StringComparison.OrdinalIgnoreCase))
{
valueProvider = () =>
{
// get connectionstring based on HttpContext.Current.Request.Url.Host
return String.Empty;
};
}
return valueProvider != null;
}
}
Then register your Parameter using a Module
public class ConnectionStringModule : Autofac.Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing += registration_Preparing;
}
private void registration_Preparing(Object sender, PreparingEventArgs e)
{
Parameter[] parameters = new Parameter[] { new ConnectionStringParameter() };
e.Parameters = e.Parameters.Concat(parameters);
}
}
Module you have to register inside your container using
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
Each time Autofac have to resolve a parameter of type String named connectionString it will used the custom parameter and get your connectionstring based on what you want.
By the way this code sample use HttpContext.Current. In case of a multithreaded process it may return null. I don't recommend using HttpContext.Current for such things. You can use an intermediate class instead of accessing it, for example a IConnectionstringProvider interface.
public interface IConnectionstringProvider
{
String ConnectionString { get; }
}
public class ConnectionStringProvider : IConnectionstringProvider
{
public ConnectionStringProvider(Strong host)
{
// get connectionstring based on host
this._connectionString = String.Empty;
}
private readonly String _connectionString;
public String ConnectionString
{
get { return this._connectionString; }
}
}
Inside your Parameter you will have to change the valueProvider by
valueProvider = () =>
{
return context.Resolve<IConnectionstringProvider>().ConnectionString;
};
And finally you will have to register your IConnectionstringProvider at the beginning of the request lifetimescope :
class Program
{
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
IContainer container = builder.Build();
container.ChildLifetimeScopeBeginning += container_ChildLifetimeScopeBeginning;
}
private static void container_ChildLifetimeScopeBeginning(
Object sender, LifetimeScopeBeginningEventArgs e)
{
String host = HttpContext.Current.Request.Url.Host;
ContainerBuilder childLifetimeScopeBuilder = new ContainerBuilder();
childLifetimeScopeBuilder.RegisterInstance(new ConnectionStringProvider(host))
.As<IConnectionstringProvider>()
.SingleInstance();
childLifetimeScopeBuilder.Update(e.LifetimeScope.ComponentRegistry);
}
}
Of course there is many way to do it but you have the idea

TryUpdateModel, Entity Framework and why updating does not work?

I use Entity Framework to deal with my database, and as I googled a lot, I ran into some solution around updating my entities, here is extension method for updating entity the code :
public static void AttachUpdated(this ObjectContext context, EntityObject objectDetached)
{
if (objectDetached.EntityState == EntityState.Detached)
{
object currentEntityInDb = null;
if (context.TryGetObjectByKey(objectDetached.EntityKey, out currentEntityInDb))
{
context.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);
//(CDLTLL)Apply property changes to all referenced entities in context
//Custom extensor method
context.ApplyReferencePropertyChanges((IEntityWithRelationships)objectDetached,
(IEntityWithRelationships)currentEntityInDb);
}
else
{
throw new ObjectNotFoundException();
}
}
}
public static void ApplyReferencePropertyChanges(this ObjectContext context,
IEntityWithRelationships newEntity,
IEntityWithRelationships oldEntity)
{
foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
{
var oldRef = relatedEnd as EntityReference;
if (oldRef != null)
{
// this related end is a reference not a collection
var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
oldRef.EntityKey = newRef.EntityKey;
}
}
}
And here is the code for saving or adding new object :
public void save (Category obj)
{
OurWebSiteEntities en = new OurWebSiteEntities();
if (obj.CategoryID == -1)
{
en.AddToCategories(obj);
}
else
en.AttachUpdated(obj);
en.SaveChanges();
}
And finally this the code I use to update my category entity in asp.net mvc :
if (cat.Category.CategoryID == -1)
{
// it is a new category
// and needs some property initialization
}
else
catItem = _categoryRepo.Items.FirstOrDefault(x => x.CategoryID == cat.Category.CategoryID);
TryUpdateModel(catItem, "Category");
_categoryRepo.Save(catItem);
But unfortunately it does not work for updating any category, and I am only able to create new one.
In your repository, you could create an update function and set entity state to modified.
public abstract class Repository<T>
{
public void Update(T entity) {
Context.Entry(entity).State = EntityState.Modified;
}
}

Resources