In our project, we use following custom JsonConverter for DateTime:
public class JavaScriptDateTimeConverter: JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof (DateTime);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var dateTime = (DateTime)value;
writer.WriteStartConstructor("Date");
writer.WriteValue(dateTime.Year);
writer.WriteValue(dateTime.Month - 1);
writer.WriteValue(dateTime.Day);
writer.WriteValue(dateTime.Hour);
writer.WriteValue(dateTime.Minute);
writer.WriteValue(dateTime.Second);
writer.WriteValue(dateTime.Millisecond);
writer.WriteEndConstructor();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//some code
}
}
So, I want to use this converter in SignalR.
Here is my Startup class:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var jsonSerializer = new JsonSerializer();
jsonSerializer.Converters.Add(new JavaScriptDateTimeConverter());
GlobalHost.DependencyResolver.Register(typeof (JsonSerializer), () => jsonSerializer);
app.MapSignalR();
}
}
But when server sends some JSON with date field to client, then client is disconnected with following messages:
SignalR: Stopping connection.
SignalR: Closing the WebSocket.
SignalR: Fired ajax abort async = true.
SignalR: Stopping the monitoring of the keep alive.
What am I doing wrong?
Related
I am developing an OData API for my Asp.net core application and i want to implement caching on this.
The problem is all my endpoints will be IQueryable with a queryable services with no execution at all. so i can't implement any caching on service level
Controller
public class TagsController : ODataController
{
private readonly ITagService _tagService;
private readonly ILogger<TagsController> _logger;
public TagsController(ITagService tagService, ILogger<TagsController> logger)
{
_tagService = tagService;
_logger = logger;
}
[HttpGet("odata/tags")]
[Tags("Odata")]
[AllowAnonymous]
[EnableCachedQuery]
public ActionResult<IQueryable<Tag>> Get()
{
try
{
return Ok(_tagService.GetAll());
}
catch (Exception ex)
{
_logger.LogError(ex, "Some unknown error has occurred.");
return BadRequest();
}
}
}
So I tried to apply an extension on EnableQuery attribute to add the caching implementation on it. so i added the following
public class EnableCachedQuery : EnableQueryAttribute
{
private IMemoryCache _memoryCache;
public EnableCachedQuery()
{
_memoryCache = new MemoryCache(new MemoryCacheOptions());
}
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
//var url = GetAbsoluteUri(actionContext.HttpContext);
var path = actionContext.HttpContext.Request.Path + actionContext.HttpContext.Request.QueryString;
//check cache
if (_memoryCache.TryGetValue(path, out ObjectResult value))
{
actionContext.Result = value;
}
else
{
base.OnActionExecuting(actionContext);
}
}
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
return;
var path = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
var cacheEntryOpts = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(15));
base.OnActionExecuted(context);
_memoryCache.Set(path, context.Result, cacheEntryOpts);
}
}
the first request completed successfully and retrieved the data correctly with filters and queries applied. then when tried to add the data to cache the context.Result holds the ObjectResult and then in the second request which should be cached the value was there but with an error in executing which means that the cached value is not the final output value that should be passed to the Result
Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'ApplicationDbContext'.
============================
Update:
public class ApplicationDbContext : IdentityDbContext<User, Account, Session>, IApplicationDbContext
{
public ApplicationDbContext(
DbContextOptions options,
IApplicationUserService currentUserService,
IDomainEventService domainEventService,
IBackgroundJobService backgroundJob,
IDomainEventService eventService,
IDateTime dateTime) : base(options, currentUserService, domainEventService, backgroundJob, dateTime) { }
public DbSet<Tag> Tags => Set<Tag>();
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
var entityTypes = builder.Model.GetEntityTypes()
.Where(c => typeof(AuditableEntity).IsAssignableFrom(c.ClrType))
.ToList();
foreach (var type in entityTypes)
{
var parameter = Expression.Parameter(type.ClrType);
var deletedCheck = Expression.Lambda
(Expression.Equal(Expression.Property(parameter, nameof(AuditableEntity.Deleted)), Expression.Constant(false)), parameter);
type.SetQueryFilter(deletedCheck);
}
builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
builder.ApplySeedsFromAssembly(typeof(ApplicationDbContext).Assembly);
}
}
I need to use different Json.NET's JSON Converters depends on header.
Some think like this:
services
.AddMvcCore()
.AddJsonOptions(options =>
{
// If(my_custom_header_value == "use_first_converter")
options.SerializerSettings.Converters.Add(new FirstConverter());
// Else
//options.SerializerSettings.Converters.Add(new FirstConverter());
})
For converting depending on custom requests' header, it is impossible to setup by AddJsonOptions. You could not access the HttpContext during ConfigureServices since there is no request during this process.
For a workaround, try register IHttpContextAccessor like
public class FirstConverter : JsonConverter
{
private readonly IHttpContextAccessor _httpContextAccessor;
public FirstConverter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public override bool CanConvert(Type objectType)
{
var header = _httpContextAccessor.HttpContext.Request.Headers;
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then in ConfigureServices
services.AddMvc().AddJsonOptions(options =>
{
var httpContextAccessor = services.BuildServiceProvider().GetRequiredService<IHttpContextAccessor>();
// If(my_custom_header_value == "use_first_converter")
options.SerializerSettings.Converters.Add(new FirstConverter(httpContextAccessor));
// Else
//options.SerializerSettings.Converters.Add(new FirstConverter());
});
Check wether to convert by var header = _httpContextAccessor.HttpContext.Request.Headers;
here is my code:
Classes:
[Serializable]
public abstract class ChallengeDetailsDto : ChallengeDto
{
[JsonConverter(typeof(PagesConverter))]
public PageDto[] Pages { get; set; } = new PageDto[] { };
}
[Serializable]
public class CaseDto : ChallengeDto
{
public CaseDto()
{
Discriminator = CaseDtoDiscriminator;
}
}
Controller:
[HttpPost]
public async Task<IActionResult> CreateAsync([FromForm]CaseDetailsDto dto)
{
}
Custom Converter
internal class ChallengeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType == typeof(ChallengeDto))
{
return true;
}
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ChallengeDto challengeDto = null;
var jsonChallange = JObject.Load(reader);
var discriminator = jsonChallange.Properties().First(p => p.Name == "discriminator").Value.ToString();
if (discriminator == ChallengeDto.CaseDtoDiscriminator)
{
challengeDto = (CaseDto)JsonConvert.DeserializeObject(jsonChallange.ToString(), typeof(CaseDto));
}
/*...*/
return challengeDto;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var challenge = value as ChallengeDto;
if (challenge.Discriminator == ChallengeDto.CaseDtoDiscriminator)
{
serializer.Serialize(writer, (CaseDto)challenge);
}
/*...*/
}
}
Error:
Could not create an instance of type
'Controllers.Challenges.Dtos.PageDto'. Model bound complex types must
not be abstract or value types and must have a parameterless
constructor.
If I use [FromBody], every thing works fine. From what I can understand, using [FromForm] the binding does not use Json.
What is the best way to bind Form Values to the property in this case?
Let's say I have the following custom JsonConverter for serialization and/or deserialization:
public class VersionConverter : JsonConverter<Version>
{
public override void WriteJson(JsonWriter writer, Version value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override Version ReadJson(JsonReader reader, Type objectType, Version existingValue, bool hasExistingValue, JsonSerializer serializer)
{
string s = (string)reader.Value;
return new Version(s);
}
}
public class NuGetPackage
{
public string PackageId { get; set; }
public Version Version { get; set; }
public string Description { get; set; }
}
Let's say I have the following code snippet in my application:
NuGetPackage p1 = new NuGetPackage
{
PackageId = "Newtonsoft.Json",
Version = new Version(10, 0, 4),
Description = null
};
string json = JsonConvert.SerializeObject(p1, Formatting.Indented, new VersionConverter());
I want the Json.NET converter to Ignore the Description member variable of the NuGetPackage class.
Note: I do Not Want to use the following "marker boolean" member variable:
public bool ShouldSerializeINSERT_YOUR_PROPERTY_NAME_HERE()
{
if(someCondition){
return true;
}else{
return false;
}
}
I would rather specify the ignoring of a specific member variable somewhere
a) when my code invokes the JsonConvert.SerializeObject?
b) or within the VersionConverter code class itself?
Could someone please show me how to ignore the specific member variable in such a way?
Since NuGetPackage is fairly simple, you could just write an additional JsonConverter for NuGetPackage that serializes only the members you need, e.g.:
public class SimplifiedNuGetPackageConverter : JsonConverter
{
public override bool CanConvert(Type objectType) { return objectType == typeof(NuGetPackage); }
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var package = (NuGetPackage)value;
serializer.Serialize(writer, new { package.PackageId, package.Version });
}
}
Then serialize as follows:
var settings = new JsonSerializerSettings
{
Converters = { new VersionConverter() },
};
if (!someCondition)
settings.Converters.Add(new SimplifiedNuGetPackageConverter());
var json = JsonConvert.SerializeObject(p1, Formatting.Indented, settings);
If you are serializing multiple instances of NuGetPackage at once and need to write Description for some but not all, you could add the logic for someCondition inside WriteJson() itself:
public class ConditionalNuGetPackageConverter : JsonConverter
{
public override bool CanConvert(Type objectType) { return objectType == typeof(NuGetPackage); }
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var package = (NuGetPackage)value;
// Replace with your logic:
var someCondition = !string.IsNullOrWhiteSpace(package.Description);
if (someCondition)
serializer.Serialize(writer, new { package.PackageId, package.Version, package.Description });
else
serializer.Serialize(writer, new { package.PackageId, package.Version });
}
}
And then serialize as follows:
var settings = new JsonSerializerSettings
{
Converters = { new ConditionalNuGetPackageConverter(), new VersionConverter() },
};
var json = JsonConvert.SerializeObject(p1, Formatting.Indented, settings);
Working .Net fiddle here.
I have an ASP.Net Web API project. I am using NHibernate in this project; Fluent NHibernate to be specific. I am handling NHib session management using a custom ActionFilterAttribute. It looks like this:
public class SessionManagement : ActionFilterAttribute
{
public SessionManagement()
{
SessionFactory = WebApiApplication.SessionFactory;
}
private ISessionFactory SessionFactory { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
session.BeginTransaction();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var session = SessionFactory.GetCurrentSession();
var transaction = session.Transaction;
if (transaction != null && transaction.IsActive)
{
transaction.Commit();
}
session = CurrentSessionContext.Unbind(SessionFactory);
session.Close();
}
This was working well for my needs. However, I have recently added a custom JSON.NET MediaTypeFormatter to format the my action's resulting JSON. The problem I am having is that my ActionFilter OnActionExecuted() method is called before the MediaTypeFormatter's WriteToStreamAsync can do it's job. The result is that lazily loaded (the problem) collections are now not available because the session is closed. What is the best way to handle this? Should I remove the ActionFilter's OnActionExecuted method and just close my session in the MediaTypeFormatter?
Thanks!!
The MediaTypeFormatter is the wrong layer to close the session at because this behavior really has nothing to do with the particular formatter you're using. Here's what I recommend doing:
Derive from HttpContent and create a class that derives from ObjectContent. Override the SerializeToStreamAsync implementation to await the base implementation's SerializeToStreamAsync then close the session:
public class SessionClosingObjectContent : ObjectContent
{
private Session _session;
public SessionClosingObjectContent(Type type, object value, MediaTypeFormatter formatter, Session session)
: base(type, value, formatter)
{
_session = session;
}
protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
await base.SerializeToStreamAsync(stream, context);
// Close the session and anything else you need to do
_session.Close();
}
}
Now in your action filter, instead of closing the session, you want to replace the response Content with your new class that closes the session:
public class SessionManagement : ActionFilterAttribute
{
public SessionManagement()
{
SessionFactory = WebApiApplication.SessionFactory;
}
private ISessionFactory SessionFactory { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
session.BeginTransaction();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var session = SessionFactory.GetCurrentSession();
var response = actionExecutedContext.Response;
if (response.Content != null)
{
ObjectContent objectContent = response.Content as ObjectContent;
if (objectContent != null)
{
response.Content = new SessionClosingObjectContent(objectContent.ObjectType, objectContent.Value, objectContent.Formatter, session);
foreach (KeyValuePair<string, IEnumerable<string>> header in objectContent.Headers)
{
response.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
}
}
}
You could also choose instead to return an HttpResponseMessage with your new Content directly from your controller code wherever you need it.