I have a number of controllers with SessionStateBehavior.ReadOnly set on them to enable parallel processing of multiple ajax requests.
But I have noticed that this doesn't stop me from writing to session (unlike SessionStateBehavior.Disabled which throws an exception).
My application uses Microsoft Charting and the chart and image map are generated in response to an ajax request with the chart being stored in session until the image markup is rendered by the browser at which point the image src triggers the browser to request the chart image which is retrieved from session. So there are no issues here with attempting to read from session before its been written.
Everything works fine, I have multiple ajax requests being processed in parallel and my charts are being returned correctly so does it matter that I have declared SessionStateBehavior.ReadOnly when I am writing to session?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using System.Web.UI.DataVisualization.Charting;
using Mdl.Rcm.Web.Areas.Dashboards.Models;
using Mdl.Web.Security;
using Mdl.Persistence.UoW;
using Mdl.Rcm.Business.Dashboards;
using Mdl.Rcm.Business.Dashboards.Models;
using Mdl.Rcm.Web.Areas.Dashboards.Charts;
using Mdl.Rcm.Web.Areas.Dashboards.Charts.OrganisationConnectionsByRole;
using Mdl.Web.Mvc;
namespace Mdl.Rcm.Web.Areas.Dashboards.Controllers
{
/// <summary>
/// Controller for the Organisation Connections By Role chart.
/// </summary>
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
[RedirectingAuthorize(Roles = "Dashboards")]
[Area(IsSiteRoot = false)]
public class ChartOrganisationConnectionsByRoleController : Controller
{
private readonly IRelationshipAndRiskService relationshipAndRiskService;
private readonly IConfigurationService configurationService;
private readonly IOrganisationConnectionsByRoleChartBuilder chartBuilder;
private readonly IListService listService;
private BarConfiguration barConfiguration;
private const string TempDataKey = "OrganisationConnectionsByRoleData";
public ChartOrganisationConnectionsByRoleController(IOrganisationConnectionsByRoleChart organisationConnectionsByRoleChart, IRelationshipAndRiskService relationshipAndRiskService, IConfigurationService configurationService, IOrganisationConnectionsByRoleChartBuilder chartBuilder, IListService listService)
{
this.relationshipAndRiskService = relationshipAndRiskService;
this.configurationService = configurationService;
this.chartBuilder = chartBuilder;
this.listService = listService;
}
/// <summary>
/// Standard Organisation Connections by Role component loader
/// </summary>
/// <param name="listId">The list id.</param>
/// <param name="degree">The active degree.</param>
/// <param name="barIndex">Index of the active bar.</param>
/// <returns></returns>
[HandleAjaxError]
public ActionResult Load(int listId, int degree, int barIndex)
{
using (UnitOfWork.Start("Analysis"))
{
// Get the data
var relationshipPlanningData = GetChartDataForInitialLoadParentComponent(listId);
var connectionsFound = relationshipPlanningData.SeriesModels.Count > 0;
var listName = listService.GetListName(listId).Name;
var information = new InformationModel(degree, true) { ConnectionsFound = connectionsFound, NumberOfOrganisationsInList = relationshipPlanningData.TotalNumberOfOrganisations, ListName = listName };
return PartialView("OrganisationConnectionsByRoleComponent", new OrganisationConnectionsByRoleComponentModel { ListId = listId, ActiveDegree = degree, ActiveBarIndex = barIndex, BarConfiguration = configurationService.GetBarConfiguration(), ConnectionsFound = connectionsFound, Information = information});
}
}
/// <summary>
/// Creates the Connections by Role chart and stores it in Session then returns the chart map.
/// The chart image map is always created first, the chart is created as part of the process of creating the map.
/// </summary>
/// <returns></returns>
public ActionResult Chart(Guid chartId, int listId)
{
using (UnitOfWork.Start("Analysis"))
{
barConfiguration = configurationService.GetBarConfiguration();
var relationshipPlanningData = GetChartDataForInitialLoadChart();
if (relationshipPlanningData.SeriesModels.Count == 0)
{
return PartialView("InfoMessage", "No connections found");
}
// Set up the chart
return GenerateChart(relationshipPlanningData, listId, chartId);
}
}
private ActionResult GenerateChart(OrganisationConnectionsByRoleData relationshipPlanningData, int listId, Guid chartId)
{
var chartAndXPoints = chartBuilder.BuildChart(2, relationshipPlanningData, barConfiguration, SetActiveBarIndex(-1), listId);
// Store the chart in session for retrieval by the browser as the src on an image tag.
ChartSession.GenerateChartToSession(chartId, chartAndXPoints.Chart, Session);
// Get y data for use client side
var yPointsDataView = GetYPointsDataView(relationshipPlanningData);
// Get x co-ordinates for use client side. Must be done after the chart has been generated to session.
var xPointsView = GetXPointsView(chartAndXPoints.XPoints);
// Render the image tag and image map
return this.Chart(chartAndXPoints.Chart, xPointsView + yPointsDataView, chartId, "ConnectionsByRoleImage");
}
/// <summary>
/// Gets the chart data for use by the main component.
/// </summary>
/// <param name="listId">The list id.</param>
/// <returns></returns>
private OrganisationConnectionsByRoleData GetChartDataForInitialLoadParentComponent(int listId)
{
// This is the call from the load action so get the data and store it for use by the chart action to save the performance hit
var data = relationshipAndRiskService.GetOrganisationConnectionsByRoleChartData(listId);
TempData[TempDataKey] = data;
return data;
}
/// <summary>
/// Gets the chart data for use by the main component chart.
/// </summary>
/// <returns></returns>
private OrganisationConnectionsByRoleData GetChartDataForInitialLoadChart()
{
// This call is from the chart action so use the data that was retreived on the load action
return (OrganisationConnectionsByRoleData)TempData[TempDataKey];
}
/// <summary>
/// Return the Connections By Role chart from session.
/// </summary>
/// <returns></returns>
public ActionResult ConnectionsByRoleImage(Guid chartId)
{
var chart = ChartSession.GetChartFromSession(chartId, Session);
return File(chart, "image");
}
/// <summary>
/// Return the Connections By Role chart from session.
/// </summary>
/// <param name="listId">The list id.</param>
/// <param name="degree">The active degree.</param>
/// <param name="barIndex">Index of the active bar.</param>
/// <returns></returns>
public ActionResult ConnectionsByRoleActive(int listId, int degree, int barIndex)
{
using (UnitOfWork.Start("Analysis"))
{
barConfiguration = configurationService.GetBarConfiguration();
// Get the data
var relationshipPlanningData = relationshipAndRiskService.GetOrganisationConnectionsByRoleChartData(listId);
if (relationshipPlanningData.SeriesModels.Count == 0)
{
return PartialView("InfoMessage", "No connections found");
}
// Set up the chart
var chartAndXPoints = chartBuilder.BuildChart(SetActiveDegree(degree), relationshipPlanningData, barConfiguration, SetActiveBarIndex(-1), listId);
var ms = new MemoryStream();
chartAndXPoints.Chart.SaveImage(ms, ChartImageFormat.Png);
return File(ms.ToArray(), "image");
}
}
/// <summary>
/// Gets the graph X points and render them as a javascript object for use by the view.
/// </summary>
/// <param name="xPoints">The x points.</param>
/// <returns></returns>
private string GetXPointsView(List<float> xPoints)
{
var model = new XPointsModel
{
ChartName = "Connections By Role",
Points = xPoints
};
return this.ViewAsString("XPoints", model);
}
/// <summary>
/// Gets the Y points data and render them as a javascript object for use by the view.
/// </summary>
/// <param name="relationshipPlanningData">The relationship planning data.</param>
/// <returns></returns>
private string GetYPointsDataView(OrganisationConnectionsByRoleData relationshipPlanningData)
{
var degree1DataPoints = relationshipPlanningData.SeriesModels[0].DataPoints.Select(p => p.Y).ToList();
var degree2DataPoints = relationshipPlanningData.SeriesModels[1].DataPoints.Select(p => p.Y).ToList();
var model = new YPointsDegree1And2Model
{
ChartName = "Connections By Role",
Degree1Data = degree1DataPoints,
Degree2Data = degree2DataPoints
};
return this.ViewAsString("YPointsDegree1And2", model);
}
private int SetActiveBarIndex(int barIndex)
{
if (barIndex == -1)
{
barIndex = barConfiguration.Bars.First(b => b.IsDefaultActive).Index;
}
return barIndex;
}
private static int SetActiveDegree(int degree)
{
if (degree == -1)
{
degree = 2;
}
return degree;
}
}
}
public class ChartSession
{
public static byte[] GetChartFromSession(Guid chartId, HttpSessionStateBase session)
{
// Get the chart from session
var data = session[chartId.ToString()] as byte[];
// Clear the session
session[chartId.ToString()] = null;
return data;
}
public static void GenerateChartToSession(Guid chartId, Chart chart, HttpSessionStateBase session)
{
var ms = new MemoryStream();
chart.SaveImage(ms, ChartImageFormat.Png);
session[chartId.ToString()] = ms.ToArray();
}
SessionStateBehavior
only tells ASP.NET what locks to put on the Session
See this question: Controller SessionStateBehavior is ReadOnly and I can update Session Variable
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With