I am new in unit testing. I have a controller - StudentsController with dependency injection and there my Index() method:
public class StudentsController : Controller
{
public readonly UniversityContext _context;//Database
public StudentsController(UniversityContext context)
{
_context = context;
}//Constructor with database
// GET: Students
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
}
Next i need to write a correct unit test code, that check, if:
1) a View() have a list with my students
2) The query with students is not null.
I read about Mock objects, but I don't know how to write the correct code. My code that I wrote so far:
public class StudentsControllerTests
{
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfStudents()
{
var mockRepo = new Mock<UniversityContext>();
mockRepo.Setup(repo => repo.Students.ToList()).Returns(GetTestStudents());//There i get following error:Expression references a method that does not belong to the mocked object
var controller = new StudentsController(mockRepo.Object);
// Act
var result = controller.Index();
//// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<Student>>(
viewResult.ViewData.Model);
Assert.NotNull(model);//Second Condition
}
public List<Student> GetTestStudents()
{
var sessions = new List<Student>();
sessions.Add(new Student()
{
bDate = new DateTime(1994, 7, 2),
Name = "Test One"
});
sessions.Add(new Student()
{
bDate = new DateTime(1995, 7, 1),
Name = "Test Two"
});
return sessions;
}
}
Can someone explain me, how to correct my code?
You only need to mock the members of the context, which in this case is the .Students property. ToList is an extension method being call on the property and cannot be mocked by moq.
Also .Students is a DbSet and would need to be mocked as well.
Using the test classes from this answer :
How to mock an async repository with Entity Framework Core
The following generic extension methods were derived
public static class MockDbSetExtensions {
public static Mock<DbSet<T>> AsDbSetMock<T>(this IEnumerable<T> list) where T : class {
IQueryable<T> queryableList = list.AsQueryable();
Mock<DbSet<T>> dbSetMock = new Mock<DbSet<T>>();
dbSetMock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(queryableList.Provider);
dbSetMock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(queryableList.Expression);
dbSetMock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(queryableList.ElementType);
dbSetMock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(() => queryableList.GetEnumerator());
return dbSetMock;
}
public static Mock<DbSet<T>> ToAsyncDbSetMock<T>(this IEnumerable<T> source)
where T : class {
var data = source.AsQueryable();
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IAsyncEnumerable<T>>()
.Setup(m => m.GetEnumerator())
.Returns(new TestAsyncEnumerator<T>(data.GetEnumerator()));
mockSet.As<IQueryable<T>>()
.Setup(m => m.Provider)
.Returns(new TestAsyncQueryProvider<T>(data.Provider));
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => data.GetEnumerator());
return mockSet;
}
}
With the above utilities, update
mockRepo.Setup(repo => repo.Students.ToList()).Returns(GetTestStudents());
To
var studentsMockedDbSet = GetTestStudents().ToAsyncDbSetMock();
mockRepo.Setup(repo => repo.Students).Returns(studentsMockedDbSet.Object);
Despite the advice to pass dependencies through the constructor I've found that the development cost of having parameterless constructors and then autowiring all of the properties on everything is significantly less and makes the application much easier to develop out and maintain. However sometimes (on a view model for example) I have a property that is registered with the container, but that I don't want to populate at construction (for example the selected item bound to a container).
Is there any way to tell the container to ignore certain properties when it autowires the rest?
At the moment I'm just resetting the properties marked with an attribute in the on activated event a la:
public static IRegistrationBuilder<TLimit, ScanningActivatorData, TRegistrationStyle>
PropertiesAutowiredExtended<TLimit, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, ScanningActivatorData, TRegistrationStyle> builder)
{
builder.ActivatorData.ConfigurationActions.Add(
(type, innerBuilder) =>
{
var parameter = Expression.Parameter(typeof(object));
var cast = Expression.Convert(parameter, type);
var assignments = type.GetProperties()
.Where(candidate => candidate.HasAttribute<NotAutowiredAttribute>())
.Select(property => new { Property = property, Expression = Expression.Property(cast, property) })
.Select(data => Expression.Assign(data.Expression, Expression.Default(data.Property.PropertyType)))
.Cast<Expression>()
.ToArray();
if (assignments.Any())
{
var #action = Expression
.Lambda<Action<object>>(Expression.Block(assignments), parameter)
.Compile();
innerBuilder.OnActivated(e =>
{
e.Context.InjectUnsetProperties(e.Instance);
#action(e.Instance);
});
}
else
{
innerBuilder.OnActivated(e => e.Context.InjectUnsetProperties(e.Instance));
}
});
return builder;
}
Is there a better way to do this?
Not sure that this is a better one, but you can go from another side, register only needed properties via WithProperty syntax. Pros is that Autofac doesn't resolve unnecessary services. Here's a working example:
public class MyClass
{
public MyDependency MyDependency { get; set; }
public MyDependency MyExcludeDependency { get; set; }
}
public class MyDependency {}
public class Program
{
public static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<MyDependency>();
builder.RegisterType<MyClass>().WithPropertiesAutowiredExcept("MyExcludeDependency");
using (var container = builder.Build())
{
var myClass = container.Resolve<MyClass>();
Console.WriteLine(myClass.MyDependency == null);
Console.WriteLine(myClass.MyExcludeDependency == null);
}
}
}
public static class PropertiesAutowiredExtensions
{
// Extension that registers only needed properties
// Filters by property name for simplicity
public static IRegistrationBuilder<TLimit, TReflectionActivatorData, TRegistrationStyle>
WithPropertiesAutowiredExcept<TLimit, TReflectionActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, TReflectionActivatorData, TRegistrationStyle> registrationBuilder,
params string[] propertiesNames)
where TReflectionActivatorData : ReflectionActivatorData
{
var type = ((IServiceWithType)registrationBuilder.RegistrationData.Services.Single()).ServiceType;
foreach (var property in type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(pi => pi.CanWrite && !propertiesNames.Contains(pi.Name)))
{
// There's no additional checks like in PropertiesAutowired for simplicity
// You can add them from Autofac.Core.Activators.Reflection.AutowiringPropertyInjector.InjectProperties
var localProperty = property;
registrationBuilder.WithProperty(
new ResolvedParameter(
(pi, c) =>
{
PropertyInfo prop;
return pi.TryGetDeclaringProperty(out prop) &&
prop.Name == localProperty.Name;
},
(pi, c) => c.Resolve(localProperty.PropertyType)));
}
return registrationBuilder;
}
// From Autofac.Util.ReflectionExtensions
public static bool TryGetDeclaringProperty(this ParameterInfo pi, out PropertyInfo prop)
{
var mi = pi.Member as MethodInfo;
if (mi != null && mi.IsSpecialName && mi.Name.StartsWith("set_", StringComparison.Ordinal)
&& mi.DeclaringType != null)
{
prop = mi.DeclaringType.GetProperty(mi.Name.Substring(4));
return true;
}
prop = null;
return false;
}
}
I have a many-to-many relationship between two entities - Media and MediaCollection. I want to check if a certain Media already exists in a collection. I can do this as follows:
mediaCollection.Media.Any(m => m.id == mediaId)
However, mediaCollection.Media is an ICollection, so to me this looks like it will have to retrieve every Media in the collection from the database just to make this check. As there could be many media in a collection, this seems very inefficient. I'n thinking that I should use a method of IQueryable, but I can't see how to do this for many-to-many relationships.
How can I check for the existence of the relationship without retrieving the whole collection?
EDIT
I am generating the EF data model from my database, then using the built in VS POCO T4 templates to generate my data context and entity classes. I think the problem is that the generated code does not return EntityCollection for the navigation properties, but instead ObjectSet. ObjectSet implements IQueryable, but does not expose a CreateSourceQuery() method.
Here is a stripped down version of the relevant lines from the context:
public partial class Entities : ObjectContext
{
public const string ConnectionString = "name=Entities";
public const string ContainerName = "Entities";
#region Constructors
public Entities()
: base(ConnectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public Entities(string connectionString)
: base(connectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public Entities(EntityConnection connection)
: base(connection, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
#endregion
#region ObjectSet Properties
public ObjectSet<MediaCollection> MediaCollections
{
get { return _mediaCollections ?? (_mediaCollections = CreateObjectSet<MediaCollection>("MediaCollections")); }
}
private ObjectSet<MediaCollection> _mediaCollections;
// snipped many more
#endregion
}
And here is a stripped down version of the class for the MediaCollection entity:
public partial class MediaCollection
{
#region Primitive Properties
// snipped
#endregion
#region Navigation Properties
public virtual ICollection<Medium> Media
{
get
{
if (_media == null)
{
var newCollection = new FixupCollection<Medium>();
newCollection.CollectionChanged += FixupMedia;
_media = newCollection;
}
return _media;
}
set
{
if (!ReferenceEquals(_media, value))
{
var previousValue = _media as FixupCollection<Medium>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupMedia;
}
_media = value;
var newValue = value as FixupCollection<Medium>;
if (newValue != null)
{
newValue.CollectionChanged += FixupMedia;
}
}
}
}
private ICollection<Medium> _media;
private void FixupMedia(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Medium item in e.NewItems)
{
if (!item.MediaCollections.Contains(this))
{
item.MediaCollections.Add(this);
}
}
}
if (e.OldItems != null)
{
foreach (Medium item in e.OldItems)
{
if (item.MediaCollections.Contains(this))
{
item.MediaCollections.Remove(this);
}
}
}
}
// snip
#endregion
}
And finally, here is the FixupCollection that the template also generates:
public class FixupCollection<T> : ObservableCollection<T>
{
protected override void ClearItems()
{
new List<T>(this).ForEach(t => Remove(t));
}
protected override void InsertItem(int index, T item)
{
if (!this.Contains(item))
{
base.InsertItem(index, item);
}
}
}
You can do that but you need a context for that:
bool exists = context.Entry(mediaCollection)
.Collection(m => m.Media)
.Query()
.Any(x => x.Id == mediaId);
Edit:
If you are using ObjectContext API with proxied POCOs instead of DbContext API the former sample will not work. You can try this:
context.ContextOptions.LazyLoadingEnabled = false;
bool exists = ((EntityCollection<Media>)mediaCollection.Media).CreateSourceQuery()
.Any(x => x.Id == mediaId);
context.ContextOptions.LazyLoadingEnabled = true;
So it seems that the built in VS POCO T4 template does not generate anything equivalent to CreateSourceQuery(). No matter! We can code it ourselves. If you add the following code at to the context's .tt file and regenerate:
public ObjectQuery<T> CreateNavigationSourceQuery<T>(object entity, string navigationProperty)
{
var ose = ObjectStateManager.GetObjectStateEntry(entity);
var rm = ObjectStateManager.GetRelationshipManager(entity);
var entityType = (System.Data.Metadata.Edm.EntityType)ose.EntitySet.ElementType;
var navigation = entityType.NavigationProperties[navigationProperty];
var relatedEnd = rm.GetRelatedEnd(navigation.RelationshipType.FullName, navigation.ToEndMember.Name);
return ((dynamic)relatedEnd).CreateSourceQuery();
}
then we can check for the existence of a many-to-many as follows:
var exists = _context.CreateNavigationSourceQuery<Medium>(mediaCollection, "Media")
.Any(m => m.Id == medium.Id);
Props to Rowan's answer on Using CreateSourceQuery in CTP4 Code First for this one.
Try,
mediaCollection.CreateSourceQuery()
.Any(....
CreateSourceQuery will create IQueryable for the association.
Beginners question:
Given two classes: Myclass5 and Myclass6 how can one wire up following factory method (returned as Func)
such that
myclass5 and myclass6 instances and IMyClass that they depend on are all retrieved via autofac (assuming that these three instances are registered).
public static MyClass4 FactoryMethod(int nu)
{
if (nu == 1)
return new MyClass5(....);
if (nu == 4)
return new MyClass6(....);
throw new NotImplementedException();
}
public abstract class MyClass4
{
}
public class MyClass5 : MyClass4
{
public MyClass5(int nu, IMyClass a)
{
}
}
public class MyClass6 : MyClass4
{
public MyClass6(int nu, IMyClass a)
{
}
}
For FactoryMethod to be able to create the instances, it requires access to a container. I would suggest create a delegate type for the factory method, which makes it easy to take dependency on it. Registration goes like this:
var cb = new ContainerBuilder();
cb.RegisterType<SomeClass>().As<IMyClass>();
cb.RegisterType<MyClass5>();
cb.RegisterType<MyClass6>();
cb.Register((c, p) =>
{
var context = c.Resolve<IComponentContext>();
return new FactoryMethod(nu =>
{
var nuParameter = TypedParameter.From(nu);
switch (nu)
{
case 1:
return context.Resolve<MyClass5>(nuParameter);
case 4:
return context.Resolve<MyClass6>(nuParameter);
default:
throw new NotImplementedException();
}
});
});
var container = cb.Build();
At resolve time, you can then take a dependency on the FactoryMethod delegate type and use it to resolve instances:
var factory = container.Resolve<FactoryMethod>();
var instance5 = factory(1);
var instance6 = factory(1);
Note: the delegate instance we're creating needs a context. We cannot use the c parameter directly since that context is only temporary. Thus we must resolve a IComponentContext to "bake" into the lambda.
Update: if you would like to extract the factory implementation into a method that is not dependent on the container I would suggest the following:
public class FactoryMethodImpl
{
readonly Func<int, MyClass5> _factory5;
readonly Func<int, MyClass6> _factory6;
public FactoryMethodImpl(Func<int, MyClass5> factory5, Func<int, MyClass6> factory6)
{
_factory5 = factory5;
_factory6 = factory6;
}
public MyClass4 Create(int nu)
{
switch (nu)
{
case 1:
return _factory5(nu);
case 4:
return _factory6(nu);
default:
throw new NotImplementedException();
}
}
}
Now, change the registration code to this:
var cb = new ContainerBuilder();
cb.RegisterType<SomeClass>().As<IMyClass>();
cb.RegisterType<MyClass5>();
cb.RegisterType<MyClass6>();
cb.RegisterType<FactoryMethodImpl>().SingleInstance();
cb.Register(c=> new FactoryMethod(c.Resolve<FactoryMethodImpl>().Create));
Like several other people, I'm having problems serializing Entity Framework objects, so that I can send the data over AJAX in a JSON format.
I've got the following server-side method, which I'm attempting to call using AJAX through jQuery
[WebMethod]
public static IEnumerable<Message> GetAllMessages(int officerId)
{
SIBSv2Entities db = new SIBSv2Entities();
return (from m in db.MessageRecipients
where m.OfficerId == officerId
select m.Message).AsEnumerable<Message>();
}
Calling this via AJAX results in this error:
A circular reference was detected while serializing an object of type \u0027System.Data.Metadata.Edm.AssociationType
Which is because of the way the Entity Framework creates circular references to keep all the objects related and accessible server side.
I came across the following code from (http://hellowebapps.com/2010-09-26/producing-json-from-entity-framework-4-0-generated-classes/) which claims to get around this problem by capping the maximum depth for references. I've added the code below, because I had to tweak it slightly to get it work (All angled brackets are missing from the code on the website)
using System.Web.Script.Serialization;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System;
public class EFObjectConverter : JavaScriptConverter
{
private int _currentDepth = 1;
private readonly int _maxDepth = 2;
private readonly List<int> _processedObjects = new List<int>();
private readonly Type[] _builtInTypes = new[]{
typeof(bool),
typeof(byte),
typeof(sbyte),
typeof(char),
typeof(decimal),
typeof(double),
typeof(float),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(short),
typeof(ushort),
typeof(string),
typeof(DateTime),
typeof(Guid)
};
public EFObjectConverter( int maxDepth = 2,
EFObjectConverter parent = null)
{
_maxDepth = maxDepth;
if (parent != null)
{
_currentDepth += parent._currentDepth;
}
}
public override object Deserialize( IDictionary<string,object> dictionary, Type type, JavaScriptSerializer serializer)
{
return null;
}
public override IDictionary<string,object> Serialize(object obj, JavaScriptSerializer serializer)
{
_processedObjects.Add(obj.GetHashCode());
Type type = obj.GetType();
var properties = from p in type.GetProperties()
where p.CanWrite &&
p.CanWrite &&
_builtInTypes.Contains(p.PropertyType)
select p;
var result = properties.ToDictionary(
property => property.Name,
property => (Object)(property.GetValue(obj, null)
== null
? ""
: property.GetValue(obj, null).ToString().Trim())
);
if (_maxDepth >= _currentDepth)
{
var complexProperties = from p in type.GetProperties()
where p.CanWrite &&
p.CanRead &&
!_builtInTypes.Contains(p.PropertyType) &&
!_processedObjects.Contains(p.GetValue(obj, null)
== null
? 0
: p.GetValue(obj, null).GetHashCode())
select p;
foreach (var property in complexProperties)
{
var js = new JavaScriptSerializer();
js.RegisterConverters(new List<JavaScriptConverter> { new EFObjectConverter(_maxDepth - _currentDepth, this) });
result.Add(property.Name, js.Serialize(property.GetValue(obj, null)));
}
}
return result;
}
public override IEnumerable<System.Type> SupportedTypes
{
get
{
return GetType().Assembly.GetTypes();
}
}
}
However even when using that code, in the following way:
var js = new System.Web.Script.Serialization.JavaScriptSerializer();
js.RegisterConverters(new List<System.Web.Script.Serialization.JavaScriptConverter> { new EFObjectConverter(2) });
return js.Serialize(messages);
I'm still seeing the A circular reference was detected... exception being thrown!
I solved these issues with the following classes:
public class EFJavaScriptSerializer : JavaScriptSerializer
{
public EFJavaScriptSerializer()
{
RegisterConverters(new List<JavaScriptConverter>{new EFJavaScriptConverter()});
}
}
and
public class EFJavaScriptConverter : JavaScriptConverter
{
private int _currentDepth = 1;
private readonly int _maxDepth = 1;
private readonly List<object> _processedObjects = new List<object>();
private readonly Type[] _builtInTypes = new[]
{
typeof(int?),
typeof(double?),
typeof(bool?),
typeof(bool),
typeof(byte),
typeof(sbyte),
typeof(char),
typeof(decimal),
typeof(double),
typeof(float),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(short),
typeof(ushort),
typeof(string),
typeof(DateTime),
typeof(DateTime?),
typeof(Guid)
};
public EFJavaScriptConverter() : this(1, null) { }
public EFJavaScriptConverter(int maxDepth = 1, EFJavaScriptConverter parent = null)
{
_maxDepth = maxDepth;
if (parent != null)
{
_currentDepth += parent._currentDepth;
}
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
return null;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
_processedObjects.Add(obj.GetHashCode());
var type = obj.GetType();
var properties = from p in type.GetProperties()
where p.CanRead && p.GetIndexParameters().Count() == 0 &&
_builtInTypes.Contains(p.PropertyType)
select p;
var result = properties.ToDictionary(
p => p.Name,
p => (Object)TryGetStringValue(p, obj));
if (_maxDepth >= _currentDepth)
{
var complexProperties = from p in type.GetProperties()
where p.CanRead &&
p.GetIndexParameters().Count() == 0 &&
!_builtInTypes.Contains(p.PropertyType) &&
p.Name != "RelationshipManager" &&
!AllreadyAdded(p, obj)
select p;
foreach (var property in complexProperties)
{
var complexValue = TryGetValue(property, obj);
if(complexValue != null)
{
var js = new EFJavaScriptConverter(_maxDepth - _currentDepth, this);
result.Add(property.Name, js.Serialize(complexValue, new EFJavaScriptSerializer()));
}
}
}
return result;
}
private bool AllreadyAdded(PropertyInfo p, object obj)
{
var val = TryGetValue(p, obj);
return _processedObjects.Contains(val == null ? 0 : val.GetHashCode());
}
private static object TryGetValue(PropertyInfo p, object obj)
{
var parameters = p.GetIndexParameters();
if (parameters.Length == 0)
{
return p.GetValue(obj, null);
}
else
{
//cant serialize these
return null;
}
}
private static object TryGetStringValue(PropertyInfo p, object obj)
{
if (p.GetIndexParameters().Length == 0)
{
var val = p.GetValue(obj, null);
return val;
}
else
{
return string.Empty;
}
}
public override IEnumerable<Type> SupportedTypes
{
get
{
var types = new List<Type>();
//ef types
types.AddRange(Assembly.GetAssembly(typeof(DbContext)).GetTypes());
//model types
types.AddRange(Assembly.GetAssembly(typeof(BaseViewModel)).GetTypes());
return types;
}
}
}
You can now safely make a call like new EFJavaScriptSerializer().Serialize(obj)
Update : since version Telerik v1.3+ you can now override the GridActionAttribute.CreateActionResult method and hence you can easily integrate this Serializer into specific controller methods by applying your custom [GridAction] attribute:
[Grid]
public ActionResult _GetOrders(int id)
{
return new GridModel(Service.GetOrders(id));
}
and
public class GridAttribute : GridActionAttribute, IActionFilter
{
/// <summary>
/// Determines the depth that the serializer will traverse
/// </summary>
public int SerializationDepth { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="GridActionAttribute"/> class.
/// </summary>
public GridAttribute()
: base()
{
ActionParameterName = "command";
SerializationDepth = 1;
}
protected override ActionResult CreateActionResult(object model)
{
return new EFJsonResult
{
Data = model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
MaxSerializationDepth = SerializationDepth
};
}
}
and finally..
public class EFJsonResult : JsonResult
{
const string JsonRequest_GetNotAllowed = "This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.";
public EFJsonResult()
{
MaxJsonLength = 1024000000;
RecursionLimit = 10;
MaxSerializationDepth = 1;
}
public int MaxJsonLength { get; set; }
public int RecursionLimit { get; set; }
public int MaxSerializationDepth { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(JsonRequest_GetNotAllowed);
}
var response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
var serializer = new JavaScriptSerializer
{
MaxJsonLength = MaxJsonLength,
RecursionLimit = RecursionLimit
};
serializer.RegisterConverters(new List<JavaScriptConverter> { new EFJsonConverter(MaxSerializationDepth) });
response.Write(serializer.Serialize(Data));
}
}
You can also detach the object from the context and it will remove the navigation properties so that it can be serialized. For my data repository classes that are used with Json i use something like this.
public DataModel.Page GetPage(Guid idPage, bool detach = false)
{
var results = from p in DataContext.Pages
where p.idPage == idPage
select p;
if (results.Count() == 0)
return null;
else
{
var result = results.First();
if (detach)
DataContext.Detach(result);
return result;
}
}
By default the returned object will have all of the complex/navigation properties, but by setting detach = true it will remove those properties and return the base object only. For a list of objects the implementation looks like this
public List<DataModel.Page> GetPageList(Guid idSite, bool detach = false)
{
var results = from p in DataContext.Pages
where p.idSite == idSite
select p;
if (results.Count() > 0)
{
if (detach)
{
List<DataModel.Page> retValue = new List<DataModel.Page>();
foreach (var result in results)
{
DataContext.Detach(result);
retValue.Add(result);
}
return retValue;
}
else
return results.ToList();
}
else
return new List<DataModel.Page>();
}
I have just successfully tested this code.
It may be that in your case your Message object is in a different assembly? The overriden Property SupportedTypes is returning everything ONLY in its own Assembly so when serialize is called the JavaScriptSerializer defaults to the standard JavaScriptConverter.
You should be able to verify this debugging.
Your error occured due to some "Reference" classes generated by EF for some entities with 1:1 relations and that the JavaScriptSerializer failed to serialize.
I've used a workaround by adding a new condition :
!p.Name.EndsWith("Reference")
The code to get the complex properties looks like this :
var complexProperties = from p in type.GetProperties()
where p.CanWrite &&
p.CanRead &&
!p.Name.EndsWith("Reference") &&
!_builtInTypes.Contains(p.PropertyType) &&
!_processedObjects.Contains(p.GetValue(obj, null)
== null
? 0
: p.GetValue(obj, null).GetHashCode())
select p;
Hope this help you.
I had a similar problem with pushing my view via Ajax to UI components.
I also found and tried to use that code sample you provided. Some problems I had with that code:
SupportedTypes wasn't grabbing the types I needed, so the converter wasn't being called
If the maximum depth is hit, the serialization would be truncated
It threw out any other converters I had on the existing serializer by creating its own new JavaScriptSerializer
Here are the fixes I implemented for those issues:
Reusing the same serializer
I simply reused the existing serializer that is passed into Serialize to solve this problem. This broke the depth hack though.
Truncating on already-visited, rather than on depth
Instead of truncating on depth, I created a HashSet<object> of already seen instances (with a custom IEqualityComparer that checked reference equality). I simply didn't recurse if I found an instance I'd already seen. This is the same detection mechanism built into the JavaScriptSerializer itself, so worked quite well.
The only problem with this solution is that the serialization output isn't very deterministic. The order of truncation is strongly dependent on the order that reflections finds the properties. You could solve this (with a perf hit) by sorting before recursing.
SupportedTypes needed the right types
My JavaScriptConverter couldn't live in the same assembly as my model. If you plan to reuse this converter code, you'll probably run into the same problem.
To solve this I had to pre-traverse the object tree, keeping a HashSet<Type> of already seen types (to avoid my own infinite recursion), and pass that to the JavaScriptConverter before registering it.
Looking back on my solution, I would now use code generation templates to create a list of the entity types. This would be much more foolproof (it uses simple iteration), and have much better perf since it would produce a list at compile time. I'd still pass this to the converter so it could be reused between models.
My final solution
I threw out that code and tried again :)
I simply wrote code to project onto new types ("ViewModel" types - in your case, it would be service contract types) before doing my serialization. The intention of my code was made more explicit, it allowed me to serialize just the data I wanted, and it didn't have the potential of slipping in queries on accident (e.g. serializing my whole DB).
My types were fairly simple, and I didn't need most of them for my view. I might look into AutoMapper to do some of this projection in the future.