I'm working on a web application that contains a sequence of views which will allow the user to submit different pieces of data which will have to be submitted to a 3rd party web service on completion.
e.g
[Select Account Type] --> [Submit User Info] --> [Submit Billing
Details]
Each view is generated under a different Controller.
What is the best way to store the data between different views? As MVC is stateless, I can't just store it all in a global object until I'm ready to submit to the web service, and I think a database is overkill because I don't want to store the data any longer than it takes to submit all of it.
What is the best way to store the data until the end of the session, without e.g. storing it in the Session object?
The best way is to store the user data is in session. as session is a data dictionary held by server across application against per user session. so any activity done by user including user details must be store in session, this will allow you to access the user object any where in the application across different views controller actions.
MVC gives you advantage to use Model Binding technique, which allows you access session variable in different controller's action across application.
for example on login .....
Store user in session
create model binder
register in global.asax
access it anywhere in across the application like simple parameter.
enjoy
*********************Login Controller********************
public ActionResult Login(LoginModel model, string returnUrl)
{
//store use object in session once
Session["UserProfile"] = user;
}
*******************Create Model Binder**************************
public class UserModelBinder:IModelBinder
{
private const string sessionKey = "UserProfile";
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext) {
// get the Cart from the session
EntityUserProfile User = (EntityUserProfile)controllerContext.HttpContext.Session[sessionKey];
// create the Cart if there wasn't one in the session data
if (User == null){
User = new EntityUserProfile();
controllerContext.HttpContext.Session[sessionKey] = User;
}
// return the cart
return User;
}
}
*******************Register Model binder in global.asax***************
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
ModelBinders.Binders.Add(typeof(EntityUserProfile), new UserModelBinder());
}
********************Access model binder any where in application **************
//following is 2nd paramter using model binder in one of my controller action
[HttpPost]
public JsonResult EmailWorkOrderInvoiceToClient(string OrderNumber, EntityUserProfile user)
{
// following is the user object from the session using model binder..
var myuser=user;
}
Keeping true to MVC
I would recommend having these linked views in the same controller. You could then easily persist your earlier model data in your later models.
For example, your SubmitBillingDetailsModel model could have a SubmitUserInfoModel property etc.
E.g:
public ActionResult SubmitUserInfo (SubmitUserInfoModel model)
{
return View ("SubmitBillingDetails", new SubmitBillingDetailsModel
{
SubmitUserInfoModel = model
});
}
You could even use one model to represent all separate views and cumulate the posted values between the separate views.
E.g:
public ActionResult SelectAccountType (CompleteModel model)
{
return View ("SubmitBillingDetails", model);
}
Just make sure that you persist the all values in your later views using hidden fields (maybe make a partial to do this for the whole model).
Use JavaScript localStorage proivided it is key/value pairs in strings
Related
I have a provider hosted sharepoint add-in which uses a database at back end. This database has some roles like Employer and Employee in DB marked with a number. For example 1 for Employer and 2 for Employee and corresponding to every row a sharepoint email address.
In my add-in, I want to mark all my actions with [Authorize(Role="Employer")] attribute but I am not sure how to proceed? If I create a custom filter then, does that mean on every action, I need to call SP to get current logged in user email address -> query DB using it -> find role -> proceed or give access denied. It will consume lots of time as there is already a SPContextFilter on every action.
I was initially saving current user details in a cookie (HttpOnly set to true) but got to know that anyone can edit it using browser extension and impersonate users.
I am pretty new to MVC so any help is appreciated.
I don't see any other way around, you will have to make a DB call for the first new request and for the subsequent requests save the user and role details in some persistent object. Consider using ViewState objects and maybe check for null before proceeding to make a database call and populating the ViewState again.
Always avoid saving user details in cookie. Check the user access with the db.
Create User Access Table to check whether the user has access or not
Use Action Filter, which will execute before the Action execute.
In controller
[RoleAccess]
public class AccountController : Controller
{
public ActionResult Index()
{
your code
}
}
The [RoleAccess] is a Action filter function.
In FilterConfig.cs
public class RoleAccessAttribute : ActionFilterAttribute
{
private ApplicationDbContext db = new ApplicationDbContext();
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var userID = HttpContext.Current.User.Identity.GetUserId();
if (access not accepted)
{
//return the user to access denied page
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary {
{"controller","Error"},
{"action","Error403"}
});
}
}
}
If the access is accepted then the user is authorized to access the requested Action
hope this helps
I want to pass a big object to a controller's action from a view. Like so:
View
<div>#Html.ActionLink("Send us an email", "Index",
"Email", new { o = #Model.Exception }, null)</div>
Controller
public class EmailController : Controller
{
[AllowAnonymous]
public ActionResult Index(object o)
{
new BaseServices.Emailer().SendEmail(o);
return View();
}
}
The thing is: the object being passed is so large that I guess that MVC is unable to make an argument out of that and add it to the route table/dictionary. So, my email controller's Index action is never called. The code bombs off somewhere in between.
No, you can't do this. ASP.NET MVC is not some magic. It relies on standard HTTP and HTML. And as you know in HTTP when you are using a GET request, there's no notion of .NET objects. You cannot ask how to pass an object in a web application because this is not defined.
There's a notion of query string parameters. So that's what you can pass => simple query string parameters:
#Html.ActionLink(
"Send us an email",
"Index",
"Email",
new { id = Model.Exception.Id, text = Model.Exception.Text },
null
)
Where the magic comes is that ASP.NET MVC will now use the 2 simple query string parameters (id and text) to map them to the corresponding properties of your view model inside the controller action.
But of course for this to work ASP.NET MVC needs to know the type of the model. You cannot just use object because this type doesn't have id nor text properties.
So:
public ActionResult Index(MyViewModel o)
Now but what about sending complex types? Well, the question that you have to ask to yourself is why on the first place this type was passed to the view? Was it because tfhe user was supposed to edit some of its properties? Is so then you should use an HTML form containing input fields allowing the user to edit them.
But since you have stuck this object into an anchor then what's the point? The server could fetch this object from wherever it fetched it in the first place. So all you need is to pass a simple id to the server:
#Html.ActionLink(
"Send us an email",
"Index",
"Email",
new { id = Model.Exception.Id },
null
)
and have the controller action take this id as parameter:
public ActionResult Index(int id)
Alright now you know the id => therefore you could retrieve the corresponding entity from wherever this entity is persisted.
Now some people might suggest you storing the object into the session before rendering the view and then retrieving this object from the session. Personally I am not a big fan of the session as it introduces state into the application. This means that you can never invoke the second action without first invoking the first action. This also means that you cannot bookmark the second action into the browser favorites. This also means that if you are running your application in a web-farm you can no longer store the session in-memory => you will have to use an out-of-process storage for this session. Sessions are way too much of a hassle.
You can't really pass it in the view.
Instead, consider storing the exception in TempData in the controller that renders the view....
public ActionResult DisplayErrorAndOptionToEmailIt()
{
TempData["LastError"] = m.Exception;
return View();
}
and then when the request comes in retrieve it from temp data and email it
public ActionResult SendTheEmail()
{
var e = TempData["LastError"] as Exception;
if (e != null)
{
EmailHelper.SendExceptionEmail(e);
}
return View();
}
On a side note, it's not the best practice to store complete objects. If possible, store only what you need:
public ActionResult DisplayErrorAndOptionToEmailIt()
{
TempData["LastError"] = m.Exception.Message;
return View();
}
I was editing a project and I saw a Session[""] in one controller method and TempData[""] in another. Is there a difference between the 4 or is it just 4 ways to do the same thing.
ViewData/ViewBag - valid only for the duration of the current request. You set it in a controller action and use it in the view, then it disappears. The difference is that the first is a dictionary whereas the second is just a dynamic wrapper around this dictionary. Both point to the same data though. ViewBag was introduced in ASP.NET MVC 3.
Example:
public ActionResult Index()
{
ViewData["foo"] = "bar";
return View();
}
and inside the view you could use this value:
<div>#ViewData["foo"]</div>
Same with ViewBag but it is dynamic:
public ActionResult Index()
{
ViewBag.foo = "bar";
return View();
}
and inside the view you could use this value:
<div>#ViewBag.foo</div>
So as you can see ViewData/ViewBag are just an alternative way to pass information to a view from a controller action compared to the classic and recommended way which is using a view model:
public class MyViewModel
{
public string Foo { get; set; }
}
and then:
public ActionResult Index()
{
var model = new MyViewModel { Foo = "bar" };
return View(model);
}
and inside your strongly typed view:
#model MyViewModel
<div>#Html.DisplayFor(x => x.Foo)</div>
As you can see using view models provide a strongly typed approach in passing information to a view from a controller action.
TempData - it allows for persisting information for the duration of a single subsequent request. You store something inside TempData and then redirect. In the target controller action to which you redirected you could retrieve the value that was stored inside TempData.
Example:
public ActionResult Foo()
{
TempData["foo"] = "bar";
return RedirectToAction("bar");
}
public ActionResult Bar()
{
var value = TempData["foo"] as string;
// use the value here. If you need to pass it to the view you could
// use ViewData/ViewBag (I can't believe I said that but I will leave it for the moment)
return View();
}
ASP.NET MVC will automatically expire the value that was stored in TempData once you read it. Under the covers ASP.NET MVC persists the information into the Session.
Session - same as TempData except that it never expires - it will be valid for all requests, not a single redirect.
ASP.net MVC introduced ViewData, ViewBag, TempData, Session to pass data between controller to view.
ViewData
ViewData is implemented by using ViewDataDictionary class which stored in CurrentRequestContext. So, ViewData life-cycle will end when the current request ends.
ViewBag is also like ViewData, and only difference is it enable dynamically sharing the data using dynamics objects.
TempData is a very short-lived instance, and you should only use it during the current and the subsequent requests only.This will be handy if you want to use Redirections(RedirectToAction, RedirectToRoute, Redirect) in ASP.net MVC and pass some data among redirects. TempData stores data in Session but framework disposes the data when current and subsequent requests ends.
Session is long-lived(Never expires) data that belongs to user session.You need to be mindful when you use session variables which can be easily cause issues.
protected void Session_Start(Object sender, EventArgs e)
{
int userType = 1;
HttpContext.Current.Session.Add("_SessionUserType",userType );
}
ViewData:
Is a special dictionary inherited from ViewDataDictionary.
Used to send data from controller to view.
It's life span is the current request.
It will be destroyed if you have Redirect.
For security reasons, it's better to check it for null before usage.
The casting should be done for the operation.
ViewBag:
Is a dynamic type (this type is presented in c#4).
Like ViewData is used to send data from the controller to the view.
The duration of the validity of its values in the current request.
In redirection between pages, its value will be null.
For security reasons before use, check it for null.
The casting is not necessary, so it's more faster than ViewData.
TempData:
A special kind of dictionary derived from TempDataDictionary.
It has Short life time, and used to send information between pages (Redirect).
After rendering the View completely, its value will be null.
For security reasons before use, check it for null.
The casting should be done for the operation.
Session:
used To send information between different requests.
Its value is not null not null values; Unless after a certain time (session expire).
For security reasons before use, check it for null.
The casting should be done for the operation.
This article explains the difference between ViewData, ViewBag and TempData. I hope you can refer this article for your need.
I write a controller like below:
public class AccountController : Controller
{
public ActionResult Login(/*---*/)
{
GenericIdentity identity = new GenericIdentity("userName");
GenericPrincipal principal = new GenericPrincipal(identity, new string[] { "role1", "role2" });
this.HttpContext.User = principal;
/*---*/;
}
}
After login, I can get user name by User.Identity.Name in other controller.
But User.IsInRole("role1") always return false.
How can I assign a value to User, I don't want to use Membership...
You need to persist the user data somewhere so that all subsequent page requests have access to it. Usually you would create an authentication ticket and store it in a cookie. Then for each request you extract the data and create your IPrincipal. This can be done in the Application_AuthenticateRequest method of Global.ascx,
MVC - How to store/assign roles of authenticated users has more information on a simple way to do what you want.
Hm.
Using membership?
At least the lower level API. You need to assign it a principal in some event (which basically turns into a cookie and is deserialized with every call).
Details are in http://support.microsoft.com/kb/306590
Or also in http://msdn.microsoft.com/en-us/library/aa302399.aspx
I know similar questions have been posed before, but I could not find a duplicate that handles ASP.NET MVC (and the controller aspect) specifically.
My question is as follows:
In a MVC model, my understanding is that the controller should handle the use of HttpContext to determine who is logged on, if any. This is so the controller can present this information to the view, so that the view itself does not have to perform these look-ups.
Is there any defacto standard on how to do it?
My current setup is as follows [simplified]
I have a BaseController, which all my other controllers inherit.
In BaseController, I perform the following override:
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
ViewData["IsUserLoggedIn"] = IsUserLoggedIn; // bool property checking HttpContext.User.Identity.IsAuthenticated;
ViewData["CurrentUser"] = CurrentUser; // Property returning logged in user, or null.
}
Then, naturally, in my Views I can check the ViewData values.
How do you do it? I'd like to know if something is wrong with my setup, and if so, what's wrong? I'm not 100% comfortable with my solution, - mainly because I'm not too familiar with the cycle, and I'm uncertain whether I've placed my code at the correct place.
I'm aware there might not be "one answer binds them all" here, I'm accepting the answer that offers most insight.
I put an encrypted element into a cookie usually. That element can be anything, but I generally make it the username.
Then in Global.asax.cs I implement Application_AuthenticateRequest. Each time a page is loaded this method is called by the system. In that method I check the cookie, if it exists, I try to load up the user. If the user loads successfully, then I know I have a logged in user, and I set the current Threads current Principal property to that user.
Once the CurrentPrincipal is set on the current thread you can access that user from your Controller, your View, your Business Layer, anywhere in your execution path.
If I can't use cookies for some reason, then I'll pass it across in ViewData (again encrypted just in case), and store it in hidden variables. Then when Controller::OnActionExecuting runs I do the same work I typically did in AuthenticateRequest (i.e. load the user and put it on the thread).
I have a BaseController class that all my controllers inherit from. It has a "CurrentUser" property that is stored in the session. The full code for my controller has a little more logic for retrieving the user, but this is the basic idea.
public class BaseController : Controller
{
User _currentUser = null;
public User CurrentUser
{
get
{
if (_currentUser == null)
_currentUser = (User)HttpContext.Session["CurrentUser"];
return _currentUser;
}
set
{
_currentUser = value;
HttpContext.Session["CurrentUser"] = value;
}
}
}
}
My models all inherit from a BaseModel class which also has a CurrentUser property.
public class BaseModel
{
public User CurrentUser { get; set; }
}
public class HomeIndexData : BaseModel
{
}
Then my controllers pass the user to the model which allows me to have strongly-typed views.
[HttpGet]
public ActionResult Index()
{
HomeIndexData data = new HomeIndexData();
data.CurrentUser = this.CurrentUser;
return View(data);
}
With this technique, I can also have a strongly-typed master page using BaseModel.
<%# Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage<BaseModel>" %>