Hold a data structure over several pages? - c#

I'm using .NET 3.5 and I need some help with how to hold a data object when you transfer between different pages.
This is my setup:
I have a four step registration where each step includes its own web page. I want to be able to hold a object in memory between the pages without saving it to the database. Each page will add something to this object.
Just for the ease of it, lets say I have a object like
public class MyObject
{
private int myNumber;
private String myName;
private List<Person> myFriends; //Person is simply a class that has a Strign name with getter and setters.
public MyObject()
{
myFriends = new List<Person>();
}
public void setMyNumber(int i){
myNumber = i;
}
public void setMyName(String n)
{
myName = n;
}
public void setMyFriends(List<Person> li)
{
myFriends = li;
}
public void addFriend(Person p)
{
myFriends.Add(p);
}
}
When I then get to the last page and have collected all the data, then I will commit it to my database. What is the best way to do this in c#? Any nice links or samples would be great!

You can use session/cookie to hold the data.
See the sample code of how to use session below.
How to: Save Values in Session State
string firstName = "Jeff";
string lastName = "Smith";
string city = "Seattle";
Session["FirstName"] = firstName;
Session["LastName"] = lastName;
Session["City"] = city;
How to: Read Values from Session State
string firstName = (string)(Session["First"]);
string lastName = (string)(Session["Last"]);
string city = (string)(Session["City"]);
Reference:
http://msdn.microsoft.com/en-us/library/ms178581.aspx#CodeExamples

Http is stateless protocol, you can use Session variable and store the object of Person into Session["variable"] = [Object] and access it on the last page.

Store the data in session for example
Session["Test"] = "Hello World"; //store in session
string str = (string)Session["Test"];//retrieving from session
So, once the value is stored in session, it can be retrieved from other pages.

If you're using in-process session state mode, you'd be fine storing such object in session.
Otherwise, you'd need to taylor some custom approach, because SQL and StateServer modes will be serializing and unserializing that object every time you want to retrieve and store it - less optimal -. Use session state, even in this operational modes if your object is as simple as having strings, integers or any other basic type (that wouldn't be a problem in terms of performance).
I guess if it's some wizard you won't need to store large data, so you could be fine with session state.
Perhaps you want to store text, or some other big things, and I believe best solution would be take a look to Windows Workflow Foundation which has a persistent storage API so you can manage a workflow (like your wizard) and save some state after finishing some step, but I repeat, this could be a good overkill. Take this advice if this user registration wizard needs a lot of information.

Related

Dynamics CRM Online Object caching not caching correctly

I have a requirement where we need a plugin to retrieve a session id from an external system and cache it for a certain time. I use a field on the entity to test if the session is actually being cached. When I refresh the CRM form a couple of times, from the output, it appears there are four versions (at any time consistently) of the same key. I have tried clearing the cache and testing again, but still the same results.
Any help appreciated, thanks in advance.
Output on each refresh of the page:
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125410:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
To accomplish this, I have implemented the following code:
public class SessionPlugin : IPlugin
{
public static readonly ObjectCache Cache = MemoryCache.Default;
private static readonly string _sessionField = "new_sessionid";
#endregion
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
try
{
if (context.MessageName.ToLower() != "retrieve" && context.Stage != 40)
return;
var userId = context.InitiatingUserId.ToString();
// Use the userid as key for the cache
var sessionId = CacheSessionId(userId, GetSessionId(userId));
sessionId = $"{sessionId}:{Cache.Select(kvp => kvp.Key == userId).ToList().Count}:{userId}";
// Assign session id to entity
var entity = (Entity)context.OutputParameters["BusinessEntity"];
if (entity.Contains(_sessionField))
entity[_sessionField] = sessionId;
else
entity.Attributes.Add(new KeyValuePair<string, object>(_sessionField, sessionId));
}
catch (Exception e)
{
throw new InvalidPluginExecutionException(e.Message);
}
}
private string CacheSessionId(string key, string sessionId)
{
// If value is in cache, return it
if (Cache.Contains(key))
return Cache.Get(key).ToString();
var cacheItemPolicy = new CacheItemPolicy()
{
AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration,
Priority = CacheItemPriority.Default
};
Cache.Add(key, sessionId, cacheItemPolicy);
return sessionId;
}
private string GetSessionId(string user)
{
// this will be replaced with the actual call to the external service for the session id
return DateTime.Now.ToString("yyyyMMdd_hhmmss");
}
}
This has been greatly explained by Daryl here: https://stackoverflow.com/a/35643860/7708157
Basically you are not having one MemoryCache instance per whole CRM system, your code simply proves that there are multiple app domains for every plugin, so even static variables stored in such plugin can have multiple values, which you cannot rely on. There is no documentation on MSDN that would explain how the sanboxing works (especially app domains in this case), but certainly using static variables is not a good idea.Of course if you are dealing with online, you cannot be sure if there is only single front-end server or many of them (which will also result in such behaviour)
Class level variables should be limited to configuration information. Using a class level variable as you are doing is not supported. In CRM Online, because of multiple web front ends, a specific request may be executed on a different server by a different instance of the plugin class than another request. Overall, assume CRM is stateless and that unless persisted and retrieved nothing should be assumed to be continuous between plugin executions.
Per the SDK:
The plug-in's Execute method should be written to be stateless because
the constructor is not called for every invocation of the plug-in.
Also, multiple system threads could execute the plug-in at the same
time. All per invocation state information is stored in the context,
so you should not use global variables or attempt to store any data in
member variables for use during the next plug-in invocation unless
that data was obtained from the configuration parameter provided to
the constructor.
Reference: https://msdn.microsoft.com/en-us/library/gg328263.aspx

Issue with WebMethod being Static in C# ASP.NET Codebehind

Due to a problem caused by having multiple forms on a single page, I used an AJAX call to a WebMethod to submit my form instead of using ASP controls. However, in doing this, the previous method I had used to create a new entry into my database no longer works because a WebMethod must be static.
I have authenticated my user already using ASPX authentication, and am trying to retrieve the username and ID of that user with codebehind. The user has already been authenticated on Page_Load, but it seems I cannot access this information through my WebMethod. Is this possible to do inside of a static WebMethod? Thank you for all of your help in advance!
[WebMethod]
public static void CreateJob()
{
Submit_Job();
}
public static void Submit_Job()
{
if (Page.User.Identity.IsAuthenticated)
{
try
{
string username = Context.User.Identity.Name;
}
catch
{
Context.GetOwinContext().Authentication.SignOut();
}
}
var manager = new UserManager();
var usernameDatabase = new ApplicationUser() { UserName = username };
usernameDatabase = manager.Find(username, "password here");
if (usernameDatabase != null)
{
IdentityHelper.SignIn(manager, usernameDatabase, isPersistent: false);
string jobTitle = Request.Form["jobTitle"];
using (var ctx = new CreateUserContext(ConfigurationManager.ConnectionStrings["myconnectionstring"].ConnectionString))
{
Job job = new Job()
{
job_title = jobTitle
};
ctx.Jobs.Add(job);
ctx.SaveChanges();
}
}
}
Edit:
There are errors for example with Page.User.Identity.IsAuthenticated -- Page, Context, and Request all appear that they cannot be static.
The specific error:
(An object reference is required for the non-static field, method, or property 'Control.Page') as well as with Context and Request.
Moving it from a simple comment
I had the same issue recently.
Luckily, whenever a user signs in our application, we store the user information encrypted into a session variable, so I retrieve that information, pass it to our user's class constructor, which decrypts it and I can use my logged in users info without a hassle.
So, my solution is to store the users info in the Session, but be careful what you store. Maybe serialize the users object and store in the session, then, whenever you need it
public void Page_Load()
{
// Retrieve authenticated user information
UserClass userObject = GetUserCredentials();
// Call a method that turns the authenticated user object into a string that contains the users session information. Given the sensivity of this information, might want to try to encrypt it or offuscate it. Store it in a session variable as a string
Session["UserContext"] = userObject.SerializeUser()
/* rest of the page code goes here */
}
[WebMethod(EnableSession=true)]
public static void CreateJob()
{
Submit_Job();
}
public static void Submit_Job()
{
// Lets get the authenticated user information through the session variable. Due to the static nature of the method, we can't access the Session variables directly, so we call it using the current HttpContext
string serializedUserInfo = )HttpContext.Current.Session["UserContext"].ToString();
// Let's create the users object. In my case, we have a overcharged constructor that receives the users serialized/encrypted information, descrypts it, deserializes it, and return a instance of the class with the deserialized information
UserClass userObject = new UserClass(serializedUserInfo);
// Do whatever the method has to do now!
}
On the subject of serialization, a quick google search with "c# object serialization" will bring you several good matches. XML and JSON are 2 of the most used kind of serialization, specially on web methods. Binary serialization is a good option to also obfuscate information of the logged in user

Holding Values in an object for console application C#

I am currently writing an application where I am getting data from the DB and passing it to the object which will later be used to send the data off else where. The issue I am having in testing is that the data doesn't hold in the object as when it is used later, I get null value errors. I know we can store these in sessions, but I also know you can use objects and have done it before at previous jobs, but do not recall what I am missing to maintain the information. Would I need to pass the object(s) from method to method until the job is done?
the first sample here shows where it is prepping the object.
public void FtpInitialize()
{
_LogController.LogToFile(ValidateMessage.BeginInitialization);
//Loading FTPClient object with ClientInfo Object data we got from DataBase. FTPClient object is the object that sends the data.
_LogController.FTPTraceLogToFile();
ClientInfo = _RepositoryController.GetClientInfo(ClientInfo);
if (ClientInfo == null)
ClientInfo.ClientDataExists = false;
else
{
FTPClient.Host = ClientInfo.Host;
FTPClient.Port = ClientInfo.Port;
FTPClient.EncryptionMode = FtpEncryptionMode.Explicit;
FTPClient.Credentials = new NetworkCredential(ClientInfo.UserName, ClientInfo.Password);
FTPClient.DataConnectionType = FtpDataConnectionType.EPSV; //according library documentation, auto passive is broken as of 10/20/2016
FTPClient.EnableThreadSafeDataConnections = ClientInfo.EnableThreadSafeConnection;
FTPClient.DataConnectionConnectTimeout = ClientInfo.DataConnectionTimeout;
FTPClient.DataConnectionReadTimeout = ClientInfo.DataReadTimeout;
FTPClient.ConnectTimeout = ClientInfo.ConnectionTimeout;
FTPClient.ReadTimeout = ClientInfo.ReadTimeout;
FTPClient.SocketPollInterval = ClientInfo.SocketPollInterval;
FTPClient.SocketKeepAlive = ClientInfo.KeepSocketAlive;
FTPClient.ValidateCertificate += new FtpSslValidation(OnValidateCertificate);
_LogController.LogToFile(ValidateMessage.ClientDataLoaded);
ClientInfo.ClientDataExists = true;
}
}
then below when I hit this part, it shows as null
public bool SendData()
{
short errorCount = 0;
using (FTPClient)
{
if (ClientInfo.ClientDataExists)
{
This looks like you are using local variables in your methods. This is why the data "disappears".
For example if you are pulling data from your database you should have some kind of model class and container so that the data can persist until you are done with it.
EX: You have a database that contains information on people (name, dob, etc).
You should have a class that defines person and possibly a List to store said people. You call your database and pull X person objects into your List variable. You then can pass your List into the methods. Note: You can ditch the List (or whatever collection you use) if you are doing it one at a time.
The implementation of this really depends on how you want the objects to persist. If you know you will only pull one person object at a time you can declare var databasePerson from the database. Use the List if you will be pulling more information and then pop off the object when your are down with it.
Hope that helps.

Update Cache with one item C#

I am getting users and their data from external webservice. I cache those items because I don't want to hit web service every time. Now, If user update any of their information, I am saving it through webservice. But I don't want to get the latest data from web service as it takes lot of time. Instead I want to update my cache. Can I do that ? If so, what would be the best way ? Here is my Code
List<User> users = appSecurity.SelectUsers();
var CacheKey = string.Format("GetUserList_{0}", currentUser);
CacheFactory.AddCacheItem(CacheKey, users, 300);
CacheFactory is a class where I handle Adding, Clearing and Removing cache. Below is the code
public static void RemoveCacheItem(string key)
{
Cache.Remove(key);
}
public static void ClearCache()
{
System.Collections.IDictionaryEnumerator enumerator = Cache.GetEnumerator();
while (enumerator.MoveNext())
{
RemoveCacheItem(enumerator.Key.ToString());
}
}
public static void AddCacheItem<T>(string key, T value, double timeOutInSeconds)
{
var Item = GetCacheItem<T>(key);
if (Item != null)
{
RemoveCacheItem(key);
Item = value;
}
Cache.Insert(key, value, null, DateTime.Now.AddSeconds(timeOutInSeconds), System.Web.Caching.Cache.NoSlidingExpiration);
}
The answer is yes, it can be done. It can also be done in many different ways depending on what you want to solve. At the basic level you can create a cache by using a List<T> or Dictionary<T,T> to store your data.
When you get information from the external web-service, you push the data into your List or Dictionary. You can then use that data throughout your application. When you need to update that cache, you update the value in the List/Dictionary.
You can update your dictonary like so
Dictionary<string, int> list = new Dictionary<string, int>();
then you can set the value for the key "test" as follows
list["test"] = list["test"] + 1;
When you are ready to push the updated data to the external source. All you need to do is properly parse that data into the format the source is expecting and send away.
Like I said there are many different ways to do this, but this is a basic sample way to accomplishing it. You can use this example to build off and go from there.

Storing global variables in c#?

I basically have created a class which when a user logs into a website it then queries the database and stores some settings in a List (So I have key/pair values).
The reason for this is because I want to always be able to access these settings without going to the database again.
I put these in a class and loop through the fields via a SQL query and add them to the list.
How can I then access these variables from another part of the application? or is there a better way to do this? I'm talking server side and not really client side.
Here is an example of what I had at the moment:
public static void createSystemMetaData()
{
string constring = ConfigurationManager.ConnectionStrings["Test"].ConnectionString;
SqlConnection sql = new SqlConnection(constring);
sql.Open();
SqlCommand systemMetaData = new SqlCommand("SELECT * FROM SD_TABLES", sql);
//Set Modules
using (SqlDataReader systemMetaDataReader = systemMetaData.ExecuteReader())
{
while (systemMetaDataReader.Read())
{
var name = systemMetaDataReader.GetOrdinal("Sequence").ToString();
var value = systemMetaDataReader.GetOrdinal("Property").ToString();
var Modules = new List<KeyValuePair<string, string>>();
Modules.Add(new KeyValuePair<string, string>(name, value));
}
}
}
Thanks
Any static properties of a class will be preserved for the lifetime of the application pool, assuming you're using ASP.NET under IIS.
So a very simple class might look like:
public static class MyConfigClass
{
public static Lazy<Something> MyConfig = new Lazy<Something>(() => GetSomethings());
public static Something GetSomethings()
{
// this will only be called once in your web application
}
}
You can then consume this by simply calling
MyConfigClass.MyConfig.Value
For less users you can go with the SessionState as Bob suggested,however with more users you might need to move to a state server or load it from Data Base each time.
As others have pointed out, the risk of holding these values in global memory is that the values might change. Also, global variables are a bad design decision as you can end up with various parts of your application reading and writing to these values, which makes debugging problems harder than it need be.
A commonly adopted solution is to wrap your database access inside a facade class. This class can then cache the values if you wish to avoid hitting the database for each request. In addition, as changes are routed through the facade too, it knows when the data has changed and can empty its cache (forcing a database re-read) when this occurs. As an added bonus, it becomes possible to mock the facade in order to test code without touching the database (database access is notoriously difficult to unit test).
From the looks of things you are using universal values irrespective of users so an SqlCacheDependency would be useful here:
Make sure you setup a database dependency in web.config for the name Test
public static class CacheData {
public static List<KeyValuePair<string,string>> GetData() {
var cache = System.Web.HttpContext.Current.Cache;
SqlCacheDependency SqlDep = null;
var modules = Cache["Modules"] as List<KeyValuePair<string,string>>;
if (modules == null) {
// Because of possible exceptions thrown when this
// code runs, use Try...Catch...Finally syntax.
try {
// Instantiate SqlDep using the SqlCacheDependency constructor.
SqlDep = new SqlCacheDependency("Test", "SD_TABLES");
}
// Handle the DatabaseNotEnabledForNotificationException with
// a call to the SqlCacheDependencyAdmin.EnableNotifications method.
catch (DatabaseNotEnabledForNotificationException exDBDis) {
SqlCacheDependencyAdmin.EnableNotifications("Test");
}
// Handle the TableNotEnabledForNotificationException with
// a call to the SqlCacheDependencyAdmin.EnableTableForNotifications method.
catch (TableNotEnabledForNotificationException exTabDis) {
SqlCacheDependencyAdmin.EnableTableForNotifications("Test", "SD_TABLES");
}
finally {
// Assign a value to modules here before calling the next line
Cache.Insert("Modules", modules, SqlDep);
}
}
return modules;
}

Categories