ASP.NET [ScriptIgnore] doesn't work on related objects - asp.net

the [ScriptIgnore] attribute works fine for me on the direct object that is getting serialized, but if I put it on a property of a related object (that is referenced from a property on the direct object), it doesn't apply?
i.e Json(user)
I.E
class user {
Badges badges
}
class Badge {
[ScriptIgnore]
SomeObject obj; //Causes circular reference error because scriptignore doesn't apply
}
Is there a way to get around this?

Is there a way to get around this?
I would recommend you using a view model exposing only the properties you need and passing the view model to the Json method.
If you don't want to follow the view model approach I recommend then the [ScriptIgnore] attribute should also work for you.
Example:
using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;
public class User
{
public IEnumerable<Badge> Badges { get; set; }
}
public class Badge
{
[ScriptIgnore]
public User User { get; set; }
}
public class Program
{
static void Main()
{
var user = new User();
var badge = new Badge { User = user };
user.Badges = new[] { badge };
var serializer = new JavaScriptSerializer();
Console.WriteLine(serializer.Serialize(user));
}
}
If you remove the [ScriptIgnore] attribute from the User property on the Badge class JSON serialization will fail due to circular reference error.

Related

Ignoring implemented interface property for all implementations [duplicate]

I have an interface with a property like this:
public interface IFoo {
// ...
[JsonIgnore]
string SecretProperty { get; }
// ...
}
I want the SecretProperty to be ignored when serializing all implementing classes. But it seems I have to define the JsonIgnore attribute on every implementation of the property. Is there a way to achieve this without having to add the JsonIgnore attribute to every implementation? I didn't find any serializer setting which helped me.
After a bit of searching, I found this question:
How to inherit the attribute from interface to object when serializing it using JSON.NET
I took the code by Jeff Sternal and added JsonIgnoreAttribute detection, so it looks like this:
class InterfaceContractResolver : DefaultContractResolver
{
public InterfaceContractResolver() : this(false) { }
public InterfaceContractResolver(bool shareCache) : base(shareCache) { }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
var interfaces = member.DeclaringType.GetInterfaces();
foreach (var #interface in interfaces)
{
foreach (var interfaceProperty in #interface.GetProperties())
{
// This is weak: among other things, an implementation
// may be deliberately hiding an interface member
if (interfaceProperty.Name == member.Name && interfaceProperty.MemberType == member.MemberType)
{
if (interfaceProperty.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any())
{
property.Ignored = true;
return property;
}
if (interfaceProperty.GetCustomAttributes(typeof(JsonPropertyAttribute), true).Any())
{
property.Ignored = false;
return property;
}
}
}
}
return property;
}
}
Using this InterfaceContractResolver in my JsonSerializerSettings, all properties which have a JsonIgnoreAttribute in any interface are ignored, too, even if they have a JsonPropertyAttribute (due to the order of the inner if blocks).
In more recent versions of Json.NET, applying [JsonIgnore] to interface properties now just works and successfully prevents them from being serialized for all implementing types, as long as the property is declared on the same class where the interface is declared. A custom contract resolver is no longer required.
For instance, if we define the following types:
public interface IFoo
{
[JsonIgnore]
string SecretProperty { get; set; }
string Include { get; set; }
}
public class Foo : IFoo
{
public string SecretProperty { get; set; }
public string Include { get; set; }
}
Then the following test passes in Json.NET 11 and 12 (and probably earlier versions also):
var root = new Foo
{
SecretProperty = "Ignore Me",
Include = "Include Me",
};
var json = JsonConvert.SerializeObject(root);
Assert.IsTrue(json == "{\"Include\":\"Include Me\"}");// Passes
Demo fiddles here and here.
I believe this was added in Json.NET 4.0.3 despite the fact that JsonIgnore was not mentioned explicitly in the release notes:
New feature - JsonObject and JsonProperty attributes can now be placed on an interface and used when serializing implementing objects.
(The implementation can be found in JsonTypeReflector.GetAttribute<T>(MemberInfo memberInfo).)
However, as noted by Vitaly, this does not work when the property is inherited from a base class of the class where the interface is declared. Demo fiddle here.
I have found it's simplest to create a DTO of only the properties I want and serialize that object to JSON. it creates many small, context specific objects but managing the code base is much easier and I don't have to think about what I'm serializing vs what I'm ignoring.
You should add [DataContract] in front of the class name.
It changes the default from including all properties, to including only explicitly marked properties. After that, add '[DataMember]' in front of each property you want to include in the JSON output.

OData paging with expand issue

I'm using OData v5/Web API 2.2 to create an endpoint that will return a list of employees from each company.
My problem occurs when I try to implement server-side paging while also using the OData $expand property. When I try to make a call to
http://localhost:60067/Companies?$expand=Employees
I get an error that says "Could not find a property named 'Employees' on type 'System.Web.OData.Query.Expressions.SelectAllAndExpand_1OfCompanyApiModel'"
However, when I removed the EnableQuery attribute the call to the endpoint or when I didn't expand it works as expected. Does anyone have an idea of what I am doing wrong? I've been googling this for a while but haven't found anything.
Here are some code snippets -
Data Models:
public class CompanyApiModel
{
[Key]
public Guid CompanyGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
public List<EmployeeApiModel> Employees { get; set; }
}
public class EmployeeApiModel
{
[Key]
public Guid EmployeeGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
}
CompaniesController.cs:
[EnableQuery(PageSize = 10)] // If I comment this out everything works
//[EnableQuery] // This fails as well
public IHttpActionResult Get(ODataQueryOptions<CompanyApiModel> queryOptions)
{
var companies = GetCompanies(queryOptions);
return Ok(companies);
// return Ok(companies.AsQueryable()); // This doesn't work either
}
WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
var routingConventions = ODataRoutingConventions.CreateDefault();
routingConventions.Insert(0, new OptionsRoutingConvention());
config.MapODataServiceRoute("odata", null, GetEdmModel(), new DefaultODataPathHandler(), routingConventions);
// below code allows endpoints to respond with either XML or JSON, depending on accept header preferences sent from client
// (default in absence of accept header is JSON)
var odataFormatters = ODataMediaTypeFormatters.Create();
config.Formatters.InsertRange(0, odataFormatters);
config.EnsureInitialized();
}
public static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "Demos";
builder.ContainerName = "DefaultContainer";
builder.EntitySet<CompanyApiModel>("Companies");
builder.EntitySet<EmployeeApiModel>("Employees");
var edmModel = builder.GetEdmModel();
return edmModel;
}
}
Figured out the problem. We were overriding the EnableQuery attribute somewhere in our code and calling it EnableMappedQuery and applying it to the controller. Thus instead of having [EnableQuery(PageSize = 10)] I should have had [EnableMappedQuery(PageSize = 10)].
EnableQuery Attribute do many works,
1. it will validate the queryoption for you.
2. it will apply the queryoption for you.
3. it can add some querysettings like PageSize.
Your scenario not working is because your GetCompanies is already applied the queryoption, so when EnableQuery get the result and apply the queryoption again, it fails, it can't find the expand property, my suggestion is just return original Company and let EnableQuery do the reset of work for you, ODataQueryOption in parameter is also not needed.
If you realy do some custom work in GetCompanies and don't need EnableQuery to apply for you, you can add PageSize in ODataQuerySettings when you call method ODataQueryOptions.ApplyTo(IQueryable, ODataQuerySettings).

Change default session provider in ASP.NET

I want to change my session proviced to statically typed - I just hate typing strings because of many many errors I do.
What technology am I using? ASP.NET MVC via EXT.NET MVC
I was trying to do that using web.config but the problem is that after add session state to it visual is not going to compile my code because of that session should be using strings as keys.
I want to use session by enums such as :
public enum SessionEnum{Model}
public class Bar{
void foo(){
Session[SessionEnum.Model] = "blah";
}
}
I am aware that I can create wrapper converting enums to strings but it's not very satisfying solution for me.
public class StorageWrapper{
public object this[SessionEnum enum]{ get{return Session[enum.toString()]}; //+set
}
What I did was create static object for base class for all of my controllers and then I was able to use it across them but after closing and opening the page again I wasn't able to get values from it. I guess I should serialize them somehow but I have no idea how.
Is there any way to do that?
EDIT
My session now looks like this :
[Serializable]
public abstract class DataWrapper<T> : HttpSessionStateBase
{
Dictionary<T, object> Dictionary { get; set; } = new Dictionary<T, object>();
public object this[T a]
{
get
{
try
{
return Dictionary[a];
}
catch
{
return null;
}
}
set { Dictionary[a] = value; }
}
}
[Serializable]
public class SessionWrapper : DataWrapper<SessionNames>
{}
public enum SessionNames { Model, Login, LastOpenedFile }
It's very simple.
Create a UserSession object which does everything you want (holds your values as enum etc), instantiate it, then put it in the session.
var US = new UserSession();
US.stuff = somestuff;
Session["UserSess"] = US
Then you can just always use Session["UserSess"].stuff;
Mmmm, wouldn't you use static const string instead of an enum?
using System.Web;
public static class SessionEnum
{
public static const string Model = "_Session_Model";
public static const string Login = "_Session_Login";
public static const string LastOpenedFile = "_Session_LastOpenedFile ";
}
class test
{
void test()
{
Session[SessionEnum.Model] = "blah";
}
}

Why can't I mock out a view property in MVP using NSubstitute

i'm trying to unit test my presenter in MVP application. here is my view interface which i'm trying to mock out using NSubstitude:
public interface ICategoriesView : IBaseViewInterface
{
string CategoryName { get; }
long CategorId { get; }
long CategoryParent { get; }
IEnumerable<EntityObject> CategoryDataSource { set; }
}
here is my unit test class. i'm using NUnit framework:
[TestFixture]
public class CategoriesTests
{
[Test(Description="this is used for testing the behavior of presenter if we pass empty name.")]
public void Add_EmptyName_Fails()
{
var _view = NSubstitute.Substitute.For<ICategoriesView>();
//now here i'm supposed to get something like _view.CategoryId.Returns(2) but i don't!
//the error message says that _view.CategoryId doesn't have an extension method
//named Returns. and it's true since intellisence doesn't list it after period
}
}
i added set modifer to the view interface and it didn't work. so what is wrong?
actually i forgot to add a using NSubstitude on top of my test class.

Creating an EF CodeFirst DbContext using Roslyn

Just a little idea I'm playing with, not sure if it's viable or has much of a use.
I'm trying to generate a very basic EF Code First database using the Roslyn CTP.
Code:
var scriptEngine = new ScriptEngine(new[] { "System", "System.Core", typeof(DbContext).Assembly.Location });
var session = Roslyn.Scripting.Session.Create();
var t = scriptEngine.CompileSubmission<DbContext>(#"
using System.Data.Entity;
public class Car {
public int Id {get; set;}
public string Name {get; set; }
}
public class Context : DbContext {
public DbSet<Car> Cars {get; set; }
}
new Context();
", session);
t.Execute();
When executed I get the following exception
Exception:
The type 'Submission#0+Car' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive, nested or generic, and does not inherit from EntityObject.
Looking through the list of possible issues, I'm guessing that Roslyn is making a nested class as part of the code gen. This makes sense otherwise the "new Context();" call would need to be wrapped into a class/method of some sort. I could emit an assembly, which would confirm the above but likely wouldn't have any clues on how to write it correctly.
I also went down the route of Syntax.ClassDeclaration, but ended up with a few hundred lines of code just to make a class with 1 property and no obvious way how to instantiate that class.
Question
Is there an easy way to create a class in Roslyn that is publicly accessible (eg not nested in another class)?
You can use Roslyn to create actual DLL library that contains your type based on your source code and then use that from your script:
var classCode = #"
using System.Data.Entity;
public class Car {
public int Id { get; set; }
public string Name { get; set; }
}
public class Context : DbContext {
public DbSet<Car> Cars { get; set; }
}";
var syntaxTree = SyntaxTree.ParseCompilationUnit(classCode);
var compilation = Compilation.Create(
"car",
new CompilationOptions(assemblyKind: AssemblyKind.DynamicallyLinkedLibrary))
.AddReferences(
new AssemblyFileReference(typeof(object).Assembly.Location), // mscorlib
new AssemblyFileReference(typeof(Uri).Assembly.Location), // System
new AssemblyFileReference(typeof(IOrderedQueryable<>).Assembly.Location), // System.Data
new AssemblyFileReference(typeof(DbContext).Assembly.Location) // EntityFramework
)
.AddSyntaxTrees(syntaxTree);
var dllPath = "car.dll";
using (var stream = File.OpenWrite(dllPath))
{
compilation.Emit(stream);
}
var code = #"new Context();";
var scriptEngine = new ScriptEngine(new[] { new FileInfo(dllPath).FullName, "EntityFramework" });
var context = scriptEngine.Execute<DbContext>(code);

Resources