ASP.NET MVC Telerik reporting: Session is null - c#

I have a web page with HTML5 Telerik report viewer, which should generate and show a report in the web page.
How does Telerik report viewer get data?
You create an implementation of the class ReportControllerBase, which is a class of the Telerik framework derived from ApiController.
The class you create, which in few words is simply an ApiController, is used by the report viewer to retrieve the report, or more precisely a ReportSource.
The report viewer retrieves a ReportSource, and the ReportSource retrieves data for the report.
So, inside the ReportController you istantiate a ReportSource, you configure it so that it knows where to retrieve data, and you return it.
In my case ReportSource retrieves data thanks to a method of a class called OffersCRUD.
This method, before to call a WCF service to get data from the database, must extract the username from the session, but the problem is that HttpContext.Current.Session is null.
I know that by deafault sessions are not enabled in Web api, but I wrote some lines of code to force it using sessions, which seems to work well in general.
There are two important requests during the report generation.
Request 1: ReportController is called, the ReportSource is istantiated to use OffersCRUD as datasource and then returned. Here the session works.
Request 2: Now that that the Report Viewer has retrieved the ReportSource, it performs an other request to obtain data, and so we are now inside the class OffersCRUD. Here the session doesn't work, it is null..
So, from now I will only speak about the second request.
From the point of view of the debugger, everything starts in the following method (before this there is External code, the code which calls the method is encapsulated)
[DataObjectMethod(DataObjectMethodType.Select)]
public static PersonOfferDTO GetPersonOfferByNumber(string offerNumber)
{
PersonOfferDTO result = null;
List<IError> errors = new List<IError>();
ServiceCaller.GetDataWithServiceUsingParameter(GetPersonOffer, errors, ref result, offerNumber);
return result;
}
Then, after some lines of code, we are inside the class UserSession, which is a wapper of the session.
public static class UserSession
{
private static UserDTO _currentUser;
public static UserDTO GetCurrentUser()
{
HttpContext context = HttpContext.Current;
if(context.Session["UserLogin"] == null)
{
return null;
}
UserDTO currentUser = context.Session["UserLogin"] as UserDTO;
return currentUser;
}
public static void SaveUserStatus(UserDTO user)
{
Validate.EnsureArgumentIsNotNull(user);
HttpContext context = HttpContext.Current;
context.Session["UserLogin"] = user;
}
}
In the method UserDTO GetCurrentUser(void), the state of HttpContext.Current is what you can see in the picture below.
As you can see, Session is null, so it will fail when it will try to access Session["UserLogin"].
If you want more details you only have to write a comment here below.

Create Telerik Report => Start Process in Background Thread => UI => Load processed report from Background Thread
The background thread does not have access to the current context and the current session.
You also tightly coupled your business logic to the ASP.NET session object.
You could try a regular MVC Controller/Action instead of WebAPI, but if its called in a background thread and does not pass browser cookies then it would not have knowledge of the current session.
You could try to set the username/password as report parameters, and then pass those into your webservice in the NeedsDataSource event.
Or if its a known report, you could load and attach the data to a public property on the report and bind it in the NeedsDataSource event.

Session is not enabled in Web Api because it does not respect REST based design. As Telerik tries to fit with this design, they do not recommend to use session with Telerik Report Viewer.
You should use an ObjectDataSource component instead to pass data to the report. In this object, you can wrap a method to retrieve data, with your custom logic (but without session).

I came across similar issue today. Found the solution in Telerik knowledge base. Here it goes..
Taken from here https://docs.telerik.com/reporting/knowledge-base/how-to-pass-information-from-httpcontext-to-reporting-engine
From Telerik Product Version 12.1 18.620
To access the current user context, you can use the Telerik.Reporting.Processing.UserIdentity.Current static property. It is also possible to use the new UserIdentity object in the expression context as a global object: =UserIdentity.
With the changes introduced in Telerik Reporting 12.1.18.620 it is possible to use the UserIdentity.Context property to store user objects (for example from the HttpContext). UserIdentity.Context can be filled up with the needed values in the ReportsController by overriding the GetUserIdentity() method. Here is a sample code :
// include in the ReportsController class
protected override UserIdentity GetUserIdentity()
{
var identity = base.GetUserIdentity();
identity.Context = new System.Collections.Concurrent.ConcurrentDictionary<string, object>();
identity.Context["UrlReferrer"] = System.Web.HttpContext.Current.Request.UrlReferrer;
// Any other available information can be stored in the identity.Context in the same way
return identity;
}
The UserIdentity.Current.Context["UrlRefferer"] should then be used (instead of HttpContext.Current.Request.UrlReferrer) to access the corresponding property/information. For example, you can access the UrlRefferer as :
// can be included in the Resolve() method of the Custom Report Resolver
Uri urlReferrer = (Uri)UserIdentity.Current.Context["UrlRefferer"];

Related

MVC Page Load instantiate several controllers with shared resources

I have detected, that during loading the main page several controllers are instantiated (I think because the main page is built from several parts). The controllers instantiate the API classes to query some data through them. I was wondering how and where I could share the same API class instance between them.
I can imagine such a code:
class HomeController : Controller
{
private MyApi Api;
public HomeController()
{
this.Api = get the pervious MyApi instance form somewhere
if (this.Api == null) // 1st time
{
this.Api = new MyApi();
put this instance to somewhere to share between controllers
}
This "somewhere" is not a session, because next page load needs another MyApi instance. It must go to an object property which remains intact during the whole page load process, but is dismissed when the html result is generated. It must be really a simple thing, but I really don't know where it is :( Could somebody help me?
You can consider using Microsoft Unity Framework in your application.
Using Unity Dependency Injector you will be able to inject instances of MyApi class into the any controller and avoid writing " if (this.Api == null) " these types of checks and also managing instances of it in some Session or Application level variables, which makes code dirty.
For this specific problem "It must go to an object property which remains intact during the whole page load process, but is dismissed when the html result is generated", You can configure Unity Injected object to have a life time of "Scoped". Meaning, the object will be created once per request.
Here's is a link on configuring Unity in an asp.net core application
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2

How to obtain other Entities in C# WebCore Entity Framework

I have a C# WebCore REST service that talks to a front end and uses Entity Framework to CRUD into a database. This uses Dependency Injection to add the contexts upon startup:
services.AddDbContext<FileCacheContext>(options => options.UseSqlServer(Settings.ConnectionSetting));
services.AddDbContext<FileImportContext>(options => options.UseSqlServer(Settings.ConnectionSetting));
The functionality that I have allows a user to upload a file, which is manipulated by the server and some properties of that file are returned to the front end. The uploaded file is cached in the database (FileCacheContext).
After some time has passed, the user now wishes to confirm their action and "promote" the file from the Cache (FileCacheContext) to the Import (FileImportContext); this is done by an action in the front end that contains the id of the cached file.
This parameter is passed to a different REST Controller, which is being invoked using the FileImport context, rather than the FileCache context:
public PromoteController(FileImportContext context)
{
_context = context;
}
[HttpPost]
public void Post([FromBody] int fileCacheId)
{
...
What I now need to do is to "move" this cached file from one "context" to another, something along the lines of:
var cachedFile = _context.FileCache.Where(f => f.FileCacheId == cachedFileId).FirstOrSingle();
var importedFile = new FileImport() { FileData = cachedFile.FileData };
_context.FileImport.Add(importedFile);
_context.SaveChanges();
My issue is that I can only see the one context, so I cannot get hold of the other context to either read from or write into.
The two underlying tables have no relationship, so I cannot link them in any way to load one based upon the other.
How can I get access to another (unrelated) context in EF?
So to answer my own question, I found that I could of course include the additional contexts required when instantiating the controller:
public PromoteController(FileImportContext contextImport, FileCacheContext contextCache)
{
_contextImport = contextImport;
_contextCache = contextCache;
}
And then use these as required.
This doesn't feel like the right way to do it, but if it works, it works...

C# dotnet core 2 pass data from middleware/filter to controller method

currently we are writing a web application with dotnet core 2.
We actually create some kind of multi-hosting platform where we can register new clients based on the url passed to our application.
However currently we wanted to create a middleware/filter to validate our client's.
Actually what we wanted to do is pull an object from the database and check if it exists, if yes, we want to call the controller method and make the object accessible, if it does not exist, we actually want to abort and show an error page.
What we already have done is created a filter/middleware that does exactly that, however we couldn't figure out a way to access the object that we already pulled in our filter/middleware inside the controller method.
is there actually any documentation for doing that?
I actually tried to figure it out from:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware?tabs=aspnetcore2x
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters
but they don't describe a way to do it, only to actually do something before/after the action.
You could add the object to the context using HttpContext.Items which the docs state:
The Items collection is a good location to store data that is needed only while processing one particular request. The collection's contents are discarded after each request. The Items collection is best used as a way for components or middleware to communicate when they operate at different points in time during a request and have no direct way to pass parameters.
For example, in your middleware:
public class MySuperAmazingMiddleware
{
private readonly RequestDelegate _next;
public MySuperAmazingMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
var mySuperAmazingObject = GetSuperAmazingObject();
context.Items.Add("SuperAmazingObject", mySuperAmazingObject );
// Call the next delegate/middleware in the pipeline
return this._next(context);
}
}
Then later on in your action method, you can read the value:
var mySuperAmazingObject = (SuperAmazingObject)HttpContext.Items["mySuperAmazingObject"];
One way of doing it (not saying it's the only or the best) is to have DI inject a proxy of the object, you set the real value of the object in your middleware, then you can access it from the proxy in the controller.
Note: if you'll pass the proxy object in the method call instead of controller, don't forget to mark it with [FromServices] attribute.
Another way would be adding the object to the request Items property. but when you read it you'll need casting from object to your actual class.

Ajax call to the Asp.net Web API controller

As part of the project we have implemented ASP.Net Web API, which returns the Json data, which is consumed by the Javascript using Angular JS on the client.
Controller code is straight forward (Trimmed description):
public class CardController : ApiController
{
// code
[HttpGet]
public CardDataGetUI GetCardDataUI(int userID, int dashBoardID, int cardID)
{
// Access the application Cache object using HttpRuntime (System.Web.Caching)
var blCache = HttpRuntime.Cache;
// Create a user specific BL access key by concatenating the user ID
string userBLAccessKey = WebAPIConstant.BlUserDashboardCard + userID;
// Access the BL object stored in the Cache
accessBL = (Bl)blCache[userBLAccessKey];
// Other Code
// Fetch the data for the control being passed
cardDataUI = accessBL.GetCardDataUI(dashBoardID, cardID);
return (cardDataUI)
}
}
The above mentioned GetCardDataUI delivers the card data for different type of control like chart, map and grid on a same UI screen, so what UI does is make an Asynchronous call to all in one go, currently I have BL (business layer) object being accessed from application wide cache, which is an issue for Multi threaded access, as they would share same object, so I have converted that to a local copy and initialized the one for each call to the controller. However that is also good enough till the each ajax call is having it's unique controller instance to call the method. However in this case it seems the http call they make have same instance thus modifying the input variable of each call thus leading to unexpected result and exception, since it is modifying the internal DS at run time. It is akin to calling the static method
Ideally I did not expected a multi-threaded call to the business layer, but it seems in Angular JS client has to make such calls, they cannot be synchronous.
Currently I have resolved the situation by introducing a lock in the controller, which certainly allows one thread at a time
However was looking for a solution like each Ajax call can have it's own controller instance, when it make the http get call.
We also have an option of modifying the above mentioned controller method like:
public CardDataGetUI[] GetCardDataUI(int userID, int dashBoardID, int[] cardID)
{
// Code
}
In this case there will be one call for all cards and I will call the data fetch in a for loop, thus synchronizing the operation, but this is not much different from locking the controller, preferable will be a separate controller instance for each AJAX call
Any suggestion?

Roles/Permissions - can caching affect it?

Once authenticatated I use HttpContext.Current.User.Identity.Name; to ensure user is authorized to view a part of my site.
When I access certain parts of my site I need to get the User and get which context (organization they are logged into), url would be something like settings/supercompany/profile. where supercompany is the current context.
For each user I would need to check if they are admin in that company or a general user, if a general user then they cannot see certain things.
public class SettingsApi
{
private readonly string _userId;
private readonly string _contextId;
public SettingsApi(string userId, string contextId)
{
_userId = userId;
_contextId = contextId;
}
}
If I instantiate the class above from a controller (post or get), would caching somehow mess things up? Users role changed and I don't pick it up? Would something like the below work well?
var settings = new SettingsApi(HttpContext.Current.User.Identity.Name, currentContextId);
settings.IsAdmin();
Note: I would have used attributes to authorize but my requirements are I need to pick out the currentContext from the URL plus I need to use the class above elsewhere in my code.
Update
AuthorizeAttribute works well with caching, but the method used to authorize i.e.
protected override bool AuthorizeCore(HttpContextBase httpContext)
Will not hand me back an instance of the class I need...
Update 2 I don't want this class or an instance of this class to be cached in anyway, everytime I ask for a new instance I don't mind fetching one from the DB...
My Question - is the way I am coding ok? Will my user and his permissions NOT be cached?
It is possible, if you're not careful, to let MVC cache the output of the first request by an authenticated user. I use VaryByCustom and the current identity's name.
[OutputCache(VaryByCustom="user")]
public class SomeController : Controller
{
// etc.
}
In my Global.asax.cs I define:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom.Equals("user", StringComparison.OrdinalIgnoreCase))
{
return context.User.Identity.IsAuthenticated ? context.User.Identity.Name : string.Empty;
}
return base.GetVaryByCustomString(context, custom);
}
If you are proposing to add instances of the SettingsApi to the cache then it definitely will not work as caching is app wide and so all users will end up sharing the same SettingsApi. Using the OutputCache should be fine (as long as you dont do something like put userid in a hidden field and use [OutputCache(VaryByCustom="user")] or similar).
If you are looking to cache the SettingsApi you should do so through SessionState which is per user/session and wont affect the authentication.

Categories