I am electing to do session to store a value that I will need to call and update throughout a handful of controllers and views. I know it is possible to do something like this with a BaseViewModel.cs, or something less session-y, but I am trying to see how Session can possibly solve my needs. That out of the way, here is what I am doing:
Flow
I have a partial view that is rendered on my _layout page like so:
#Html.Action("OrgSwitch", new { controller = "Common", area = "InkScroll" })
This is a drop down list containing a logged in users organizations. It hits a CommonController that takes care of things like rendering model-bound logic on layout pages. In the CommonController I have this view and a postback, like so:
[ChildActionOnly]
public ViewResult OrgSwitch()
{
var userOrgs = new List<SelectListItem>();
var user = Ctx.Users.FirstOrDefault(x => x.UserName == User.Identity.Name);
int key = 0;
if (user.Organizations.Count > 0)
{
TempData["HasOrgs"] = true;
foreach (var org in user.Organizations)
{
if (Session["SelectedOrgKey"] == null)
{
//currently setting selected by primary org id
//todo: set selected to tempdata selectedOrgId if there is one.
userOrgs.Add(org.OrganizationId.ToString() == user.PrimaryOrgId
? new SelectListItem { Text = org.Name, Value = org.OrganizationId.ToString(), Selected = true }
: new SelectListItem { Text = org.Name, Value = org.OrganizationId.ToString(), Selected = false });
}
else
{
key = Convert.ToInt32(Session["SelectedOrgKey"]);
userOrgs.Add(org.OrganizationId == key
? new SelectListItem { Text = org.Name, Value = org.OrganizationId.ToString(), Selected = true }
: new SelectListItem { Text = org.Name, Value = org.OrganizationId.ToString(), Selected = false });
}
}
ViewBag.UserOrgs = userOrgs.OrderBy(x => x.Text);
}
else
{
ViewBag.UserOrgs = userOrgs;
TempData["HasOrgs"] = false;
}
Session["SelectedOrgKey"] = key;
return View();
}
[HttpPost]
public RedirectToRouteResult OrgSwitch(string UserOrgs)
{
Session["SelectedOrgKey"] = UserOrgs;
return RedirectToAction("Index", "Recruiter", new { orgId = UserOrgs, area = "InkScroll" });
}
This more or less on initial load will render the drop down list and default it to the users primary org. If they select something and post back that selection, it will display the selected one by default on subsequent pages.
attempt and failure
I am trying various ways of utilizing the Session value. One area in a viewresult:
[ChildActionOnly]
public ViewResult StaffList()
{
var user = Ctx.Users.FirstOrDefault(x => x.UserName == User.Identity.Name);
var model = new List<StaffListViewModel>();
int key = Convert.ToInt32(Session["SelectedOrgKey"]);
var org = user.Organizations.FirstOrDefault(x => x.OrganizationId == key);
if (org.RegisteredMembers != null)
{
foreach (var req in org.RegisteredMembers)
{
model.Add(new StaffListViewModel
{
UserName = req.UserName,
Name = (req.FirstName ?? "") + " " + (req.LastName ?? ""),
ImageLocation = req.ImageLocation
});
}
}
Session["SelectedOrgKey"] = key;
return View(model);
}
in here 'key' is coming up empty.
Another try is putting it in the layout cshtml file like so:
<li><a href="#Url.Action("Staff", "Recruiter", new {area="",
orgId = Convert.ToInt32(Session["SelectedOrgId"])})">Staff</a></li>
in this one if I hover over the link, the orgId is always equal to 0. Am I messing this up somewhere? Really, I need to have this SelectedOrgId available to any page on the application.
An answer for posterity sake:
The code in the question DOES work. The way I am setting and calling session in asp.net mvc works fine. If you have arrived here in a vain search to get session working, then keep looking I guess. The issue I was have related to my control flow and the parameter i was passing in. Once I removed the 'orgId' from the parameters above, I refactored the if/else stuff to just look at session. So the issue was that somewhere in the app I am working on, orgId was not being changed from '0'. Anyway, I am an idiot, but at least I can give you a quick overview of what the hell Session is in asp.net mvc and how easy it is to use:
Setting up app to use session
Session is defaulted to be in process with a timeout of 20 minutes. If you feel like something is overwriting that default you can set it explicitly in your web.config (root web.config on a standard asp.net mvc app):
<system.web>
<sessionState mode="InProc" timeout="60"></sessionState>
...other stuff..
</system.web>
I set my timeout above to 60 minutes since I wanted it to stay around a little longer. Standard is 20.
Setting a session variable
Pretty damn simple. Do this:
Session["MyStringVariable"] = "Some value I want to keep around";
Session["MyIntegerVariable"] = 42;
Retrieving the session variable
again, pretty simple, you just need to pay attention to casting/converting the variable to what suits you.
var localVar = Session["MyStringVariable"].ToString();
var anotherLocalVar = Convert.ToInt32(Session["MyIntegerVariable"] = 42);
So, yeah, I was doing it right, but due to other complexities in my code above, I blamed the big bad Session and not my brain.
Hope this helps someone! (And if I have the above info wrong, please let me know and I will update!).
Related
I have developed a career application page using MVC and EF where the user when loading the page gets a random unique id to which I add some extra numbers and then assign(?) it to a session id variable like below:
var uniqueID = System.Guid.NewGuid().ToString();
var tempcount = (db.Applicants.Select(x => x)).Count();
user.FormId = tempcount + "_" + uniqueID;
Session["id"] = user.FormId;
Scrolling down the form the user must complete tables that include previous work experience, qualifications etc.
To load data on each for the specific user I do the following:
public ActionResult GetData(Applicant applicant)
{
db.Configuration.LazyLoadingEnabled = false;
db.Configuration.ProxyCreationEnabled = false;
var tosee = (string)Session["id"];
IEnumerable<WorkExp> workexpList = db.WorkExps.Where(x => x.FormId == tosee).ToList();
return Json(new { data = workexpList }, JsonRequestBehavior.AllowGet);
}
For each table there is a similar method like the above.
Every once in a few months I receive complaints from users telling me they can see the data of other random applicants!
Can someone explain me why could this be happening?
I have a MVC form that is submitted with data. After submit, it goes into a preview view form where a user can decide whether to proceed posting or edit the form again.
Apart from submitting data, I also offer the option to upload a picture. I basically have 3 scenarios in this preview form:
update form and keep existing picture
update form and replace (or delete) existing picture
update form and add a picture (in case there was none before)
Now, scenario 2) and 3) work fine, but what I am struggling with is scenario 1). For some reason any existing picture (from the initial submit) would be overridden/deleted. When I tried to debug I noticed that the else clause of my if statement is simply being skipped.
I doubt this has something to do with my view or the model, hence I am posting the relevant code of my controller.
public ActionResult UpdateErrand(
[Bind(Exclude = "Picture")]errands model)
{
// define variables for reuse
var userID = User.Identity.GetUserId();
DateTime nowUTC = DateTime.Now.ToUniversalTime();
DateTime nowLocal = DateTime.Now.ToLocalTime();
// get picture and get it to bytes
string picture = Request.Form["editErrandCroppedPicture"];
byte[] imageBytes = Convert.FromBase64String(picture);
// define errand
var errand = new errands
{
// basics
UserID = userID,
FirstName = UserManager.FindById(userID).FirstName,
Email = UserManager.FindById(userID).Email,
Phone = UserManager.FindById(userID).PhoneNumber,
//Rating =
// form
ID = model.ID,
Category = model.Category,
SubCategory = model.SubCategory,
Title = model.Title,
Description = model.Description,
Location = model.Location,
ASAP = model.ASAP,
StartDateTime = model.StartDateTime,
DurationInHours = model.DurationInHours,
EndDateTime = model.EndDateTime,
DateTimePosted = nowLocal,
Currency = model.Currency,
Offering = model.Offering,
Price = model.Price,
errandTax = model.errandTax,
PaymentMethod = model.PaymentMethod,
LatitudePosted = model.LatitudePosted,
LongitudePosted = model.LongitudePosted,
LocationPosted = model.LocationPosted,
Published = true
};
// workaround to ensure picture is uploaded correctly
if (imageBytes.Length > 2)
{
errand.Picture = imageBytes;
}
else
{
errand.Picture = model.Picture;
}
// save errand to DB
ERRANDOM.Entry(errand).State = EntityState.Modified;
ERRANDOM.SaveChanges();
// track user activity: post includes activity name, timestamp along with location, and if authenticated, user ID
var SUCCESS = new UserActivities
{
UserID = userID,
ActivityName = "EditErrand_Success",
ActivityTimeStampUTC = nowUTC,
ActivityLatitude = model.LatitudePosted,
ActivityLongitude = model.LongitudePosted,
ActivityLocation = model.LocationPosted
};
ERRANDOM.UserActivityList.Add(SUCCESS);
ERRANDOM.SaveChanges();
return RedirectToAction("errandPreview");
}
If you mark an entity as modified (.State = EntityState.Modified) all of its mapped scalar properties (i.e. non-navigation properties) will be part of the SQL update statement. However, it's possible to exclude selected properties from the update. Here's how to do it:
...
LatitudePosted = model.LatitudePosted,
LongitudePosted = model.LongitudePosted,
LocationPosted = model.LocationPosted,
Published = true
errand.Picture = imageBytes;
ERRANDOM.Entry(errand).State = EntityState.Modified;
if (imageBytes.Length <= 2)
{
ERRANDOM.Entry(errand).Property(e => e.Picture).IsModified = false;
}
ERRANDOM.SaveChanges();
As you see, you can now assign errand.Picture = imageBytes unconditionally: it will be excluded later when it doesn't need updating. This also saves bandwidth in updating errands in the (presumably) most common case when no picture is updated.
You are excluding binding Picture so your IF statement else clause would be assigning a null value (model.Picture) to a "new" errands object.
I am very new to c# and asp.net mvc. I'm building a HR portal for our company where a user can submit a leave form among other things... So I'm using mssql as the database server and using Entity Frame work to communicate with it. I have 3 entities, user (Containing user details), permissions (the user permissions for allowing actions in the app) and then the leave form table (where the leave form details are stored). There is a one to many relationship between user - permission and then a one to many relationship between user-leave. I am not fazed about the permissions as that gets created when the user account is being created.
The problem I am facing is, how do I add a leave form for a specific user? Below is my controller code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Leave(MasterViewModel model)
{
DocSubViewModel mv = model.DSModel;
int userId = Convert.ToInt32(Session["userID"]);
try
{
using (HrDcpDBContainer db = new HrDcpDBContainer())
{
var leave = db.leaves.Create();
leave.dateFrom = mv.DateFrom;
leave.dateSubmitted = DateTime.Now;
leave.dateTo = mv.DateTo;
leave.nrDays = mv.NrDays;
leave.reason = mv.SpecialLeave;
leave.TLApproval = null;
leave.TLApprovalDate = null;
leave.TLApprovalID = mv.TeamLeaderID;
leave.DMApprovalDate = null;
leave.DMApprovalID = mv.DepManagerID;
leave.DMApproval = null;
leave.type = mv.Type;
leave.user = userId;
db.leaves.Add(leave);
db.SaveChanges();
}
ViewBag.Message = "Leave Form submitted Successfully. You will be redirected shortly...";
return View("result");
}
catch (Exception ex)
{
ViewBag.Message = ex;
//ViewBag.Message = "Leave Form submitted Successfully. You will be redirected shortly...";
return View("result");
}
The problem comes in leave.user = userId;. It says:
Cannot implicitly convert int to Portal.Model.user
I can't seem to find out how to do this...
You're telling it to put the UserId where your leave model is asking for a User.
Your relationship requires a User to go in there, so you'll have to update your code a little bit:
using (HrDcpDBContainer db = new HrDcpDBContainer())
{
var leave = db.leaves.Create();
leave.user = db.users.First(x => x.Id == userId);
}
This will put reference to the actual user in the new leave record. If you go later and check it out you'll see a column in the leave table called user_Id that has an integer value in it and is set as a foreign key to the users table.
Note that this will error if no user exists having the specified Id value. If you anticipate this to be a problem, rather use .FirstOrDefault() instead of .First() and then account for the value being null before you add it to your new leave object.
That's expected since User is a object and not int. What you should be doing probably is leave.user.UserId = userId; instead [Assuming leave.user is of type User which has a UserId property]
i want to display value of sharepoint people/group value in people editor(web part) when the page is loaded. This is the code that i use to get the value displayed in web part
if(SPContext .Current .ListItem .ID >= 1)
using (SPSite site = new SPSite("sitename"))
{
using (SPWeb web = site.OpenWeb())
{
var id = SPContext.Current.ListItem.ID;
SPList lists = web.Lists["DDClist"];
SPListItem item = lists.GetItemById(id);
{
string test = Convert.ToString(item["Project No"]);
tb_pno.Text = test;
string test2 = Convert.ToString(item["Project Title"]);
tb_pname.Text = test2;
string test3 = Convert.ToString(item["DDC No"]);
tb_idcno.Text = test3;
string test4 = Convert.ToString(item["Date In"]);
TextBox3.Text = test4;
}
}
}
is there a way to do the same thing with people editor?
This is all a little tricky; when I've had to do it before, I use the following to get SPUser object out of a field:
SPUser singleUser = new SPFieldUserValue(
item.Web, item["Single User"] as string).User;
SPUser[] multipleUsers = ((SPFieldUserValueCollection)item["MultipleUsers"])
.Cast<SPFieldUserValue>().Select(f => f.User);
I'm not sure why one user is stored as a string, but multiple users are stored as a specific object; it may also not be consistent in this so you might have to debug a bit and see what the type in your field is.
Once you have these SPUsers, you can populate your PeopleEditor control
using the account names as follows (quite long-winded):
ArrayList entityArrayList = new ArrayList();
foreach(SPUser user in multipleUsers) // or just once for a single user
{
PickerEntity entity = new PickerEntity;
entity.Key = user.LoginName;
entity = peMyPeople.ValidateEntity(entity);
entityArrayList.Add(entity);
}
peMyPeople.UpdateEntities(entityArrayList);
This also performs validation of the users of some kind.
If the page this control appears on may be posted-back, you need the following to be done during the postback in order for the values to be correctly roundtripped; I put it in PreRender but it could happen elsewhere in the lifecycle:
protected override void OnPreRender(EventArgs e)
{
if (IsPostBack)
{
var csa = peMyPeople.CommaSeparatedAccounts;
csa = peMyPeople.CommaSeparatedAccounts;
}
}
If you want to check any error messages that the control generates for you (if the user input is incorrect), you need to have done this switchout already:
var csa = usrBankSponsor.CommaSeparatedAccounts;
csa = usrOtherBankParties.CommaSeparatedAccounts;
//ErrorMessage is incorrect if you haven't done the above
if (!String.IsNullOrEmpty(usrBankSponsor.ErrorMessage))
{
...
}
It's really not very nice and there may be a much better way of handling it, but this is the result of my experience so far so hopefully it will save you some time.
Asked this on the forums as well, but no luck as of yet. What I need to do is set the HTML content of each content block on a given page. It seems that I can set the html value okay, but saving it does not update the actual page.
I'm wondering if it's because there needs to be some sort of save call on the control. There doesn't seem to be any methods available for such an action.
foreach (var c in duplicated.Page.Controls)
{
// go through the properties, se the ID to grab the right text
foreach (var p in c.Properties)
{
if (p.Name == "ID")
{
var content = pageContent.Where(content_pair => content_pair.Key == p.Value).SingleOrDefault();
var control = pageManager.LoadControl(c);
if (control is ContentBlock)
{
var contentBlock = pageManager.LoadControl(c) as ContentBlock;
contentBlock.Html = content.Value;
}
}
}
}
pageManager.SaveChanges(); */
WorkflowManager.MessageWorkflow(duplicated.Id, typeof(PageNode), null, "Publish", false, bag);
The following code may help you achieve what you need.
It will first get the page by its title (I am looking for a page by the title "duplicated" as it's implied by your code).
It generates a new draft of the current page, then go over its controls.
Controls which are detected as content blocks are then iterated in a foreach loop.
As written in the comment inside the foreach loop, you may detect controls by their explicit ID (by the property named "ID") or by their related shared content block (by the property named "SharedContentID") or any other condition (or ignore this condition altogether, which would result in updating all the controls n the page.
Once we have a control to update at hand, you can set its new value depending on the localization settings of your project.
After that the draft is saved and published and optionally a new version is created for it.
PageManager pageManager = PageManager.GetManager();
VersionManager vmanager = VersionManager.GetManager();
PageNode duplicated = pageManager.GetPageNodes().FirstOrDefault(p => p.Title == "duplicate");
if (duplicated != null)
{
var draft = pageManager.EditPage(duplicated.Page.Id, true);
string contentBlockTypeName = typeof(ContentBlock).FullName;
PageDraftControl[] contentBlocks = draft.Controls.Where(contentBlock => contentBlock.ObjectType == contentBlockTypeName).ToArray();
foreach (PageDraftControl contentBlock in contentBlocks)
{
Guid contentBlockId = contentBlock.Id;
//User "SharedContentID" if you are looking up controls which are linked to a shared content block of a specific ID.
//If you you are trying to locate a specific control by its own ID, use the explicit "ID" property instead of "SharedCotentID"
if (contentBlock.Properties.Where(prop => prop.Name == "SharedContentID" && prop.Value.ToString() == contentItemIdstr).FirstOrDefault() != null)
{
ControlProperty htmlProperty = contentBlock.Properties.Where(prop => prop.Control.Id == contentBlockId && prop.Name == "Html").FirstOrDefault();
if (htmlProperty != null)
{
if (AppSettings.CurrentSettings.Multilingual)
{
htmlProperty.GetString("MultilingualValue").SetString(CultureInfo.CurrentUICulture, "New Value");
}
else
{
htmlProperty.Value = "New Value";
}
}
}
}
draft = pageManager.SavePageDraft(draft);
draft.ParentPage.LockedBy = Guid.Empty;
pageManager.PublishPageDraft(draft);
pageManager.DeletePageTempDrafts(draft.ParentPage);
//Use the 2 next lines to create a new version of your page, if you wish.
//Otherwise the content will be updated on the current page version.
vmanager.CreateVersion(draft, draft.ParentPage.Id, true);
vmanager.SaveChanges();
pageManager.SaveChanges();
}
I hope this code helps.
Alon.