I am building an MVC 4 intranet site that I would like to have persistent data with in a footer. I'm going to hold a few basic stats that are unique to each user. I want this data to persist across all pages during a user's visit to the site.
What is the best best way to persist this data? A cookie? It's just a few non-senstive bits of information that will be pulled from AD. I was thinking hidden fields but that seems a bit cumbersome.
Thanks for the insight!
While you could use sessions for this task, I would caution against. Sessions should be avoided as much as possible.
You can use a child action to build the footer, using AD to get the relevant details, and then render this in your layout. If you're concerned about AD being hit every request to render the footer, then just cache the result.
public class FooController : Controller
{
...
[ChildActionOnly]
public ActionResult Footer()
{
// query AD and stuff the info in a view model
return PartialView("_Footer", model);
}
}
Then, in your layout:
#Html.Action("Footer", "Foo")
Of course, you'll need to move your current footer view code into a partial view, named _Footer.cshtml based on this example.
To cache, just prefix the child action with:
[OutputCache(Duration = 3600, VaryByCustom = "User")]
Duration is in seconds, so the above would cache for an hour. And you'll need the following method added in Global.asax:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom == "User")
{
if (context.Request.IsAuthenticated)
{
return context.User.Identity.Name;
}
return null;
}
return base.GetVaryByCustomString(context, custom);
}
Related
I'm new to ASP.Net MVC. In PHP, I always use the PRG pattern even when the post request was invalid. It was pretty easy with session flashes (also user friendly).
In ASP.Net MVC, however, I don't see an easy way to do PRG when the request is invalid. I could think of some ways, but I don't think they are good practices and put some extra unnecessary work.
Moreover, from a couple of articles that I've read, a PRG when the request was invalid was discouraged. If it's a bad practice, then what's the better way to handle unsuccessful post requests? Is it really better off without the PRG? And should I just let the rather annoying browser warnings when a user tries to refresh the page?
In Mvc, it's normal practice to handle your Post Actions as it follows:
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult LoginForm(LoginViewModel loginViewModel)
{
if (!ModelState.IsValid)
return View("Login", loginViewModel);
return Redirect("/");
}
As you can see, the property ModelState.IsValid will tell you if the request is invalid, therefore giving you the ability to return the same view and display the error messages in the ValidationSummary when the Post request contains an error. This is the code for the View:
#using (Html.BeginForm("LoginForm", "Account"}))
{
#Html.ValidationSummary() // THIS WILL SHOW THE ERROR MESSAGES
#Html.AntiForgeryToken()
#Html.TextBoxFor(x => x.Email)
#Html.PasswordFor(x => x.Password)
<button type="submit">Submit</button>
}
We have been using PRG pattern in our asp.net mvc web apps for about 5 years. The main reason we adopted PRG was to support browser navigation (eg back, forward). Our web apps are used by customer and for front/back office operations. Our typical web page flow is starts with a login, then progresses via many list/detail view. We also incorporate partial views which also have their own viewmodel. List views will have links (GETS) for navigation. Detail views will have forms (POSTS) for navigation.
Keys aspects of our PRG:
We incorporate viewmodels so each view has a viewmodel (all data access is done in the viewmodel).
Each viewmodel has a set() & get() method to maintain the key data field values associated with the most recent instance of the view. The set/get values are persisted in sessionstate.
The set method has a parameter for each value that needs to be set. The get method is just called from the viewmodel constructor to populate the viewmodel's public "key" values.
The viewmodel will also have a public load() method that get all neccessary data for its view.
Our PRG pattern overview:
In controllers we have a separate GET method and a POST method for each action. The GET only displays a view; the POST processes the posted data.
For list (menu) views, the controller GET method calls the target view's set('item key values here') method, then invokes a RedirectToAction to to the target view's controller GET action.
The controller GET method will instantiate the viewmodel (thus causing get of set values), call its load method which uses the set/get key values to get it data, and returns the view/viewmodel.
The controller POST method will either have the viewmodel save the valid posted data then redirect to the next desired page (probably the previous list menu) -OR- if redisplay the current view if the data is invalid.
I have not documented all the PRG flow senarios that we implemented, but the above is the basic flow.
SAMPLE VIEWMODEL SET/GET METHODS
private void GetKeys() {
Hashtable viewModelKeys;
if (SdsuSessionState.Exists("RosterDetail"))
{
viewModelKeys = (Hashtable)SdsuSessionState.Get("RosterDetail");
EventId = (int)viewModelKeys["EventId"];
SessionNo = (int)viewModelKeys["SessionNo"];
viewModelKeys = null;
}
}
public static void SetKeys(int eventId, int sessionNo) {
Hashtable viewModelKeys = new Hashtable();
viewModelKeys.Add("EventId",eventId);
viewModelKeys.Add("SessionNo",sessionNo);
SdsuSessionState.Set("RosterDetail",viewModelKeys);
viewModelKeys = null;
}
SAMPLE CONTROLLER
[AcceptVerbs("Get")]
public ActionResult MenuLink(int eventId, int sessionNo, string submitButton) {
if (submitButton == RosterMenu.Button.PrintPreview) {
// P-R-G: set called viewmodel keys.
RosterDetail.SetKeys(eventId,sessionNo);
// Display page.
return RedirectToAction("Detail","Roster");
}
if (submitButton == RosterMenu.Button.Export) { etc ...}
}
I'm newbie in web development.
Consider site that user can be logged in (e.f Facebook log-in)
Assuming I know if a user is logged in or not (I'm on my way to find how ;) - is it possible that on the same view (.cshtml) - part of the elements will be hidden if user anonymous or will be revealed if user is logged-in? you know - something like nice attributes or conditions (in short - to put the logic on the same view and not to manage two .cshtml)
I'm personally not a fan of having if statements in my view's as they can easily begin to get cluttered, especially if you are using roles.
This is the way I prefer to do it to avoid that.
Create an html helper containing the logic like this abstracted away:
namespace System.Web.Mvc
{
public static class HtmlHelperExtensions
{
public static MvcHtmlString UserMessage(this HtmlHelper htmlHelper)
{
string welcomeFormat = "Welcome, {0}";
var isAuthenticated = htmlHelper.ViewContext.HttpContext.User.Identity.IsAuthenticated;
var name = htmlHelper.ViewContext.HttpContext.User.Identity.Name;
var message = isAuthenticated ? string.Format(welcomeFormat, name) : string.Format(welcomeFormat, "anonymous!");
return new MvcHtmlString(message);
}
}
}
Call this method within my view like this:
<p>#Html.UserMessage()</p>
If you are new to web development the code for the Helper extension might look a bit overwhelming but you only end up writing that once and the code you use to call it elsewhere is a lot simpler and re-usable.
Here is an article about Html helpers for more info:
http://www.codeproject.com/Articles/649394/ASP-NET-MVC-Custom-HTML-Helpers-Csharp
Update
Forgot to mention this technique too, which again avoids the if statements.
Create two partial views say _auth.cshtml & _unauth.cshtml.
Create an action that checks if the user is authenticated & returns the relevant partial i.e
public ActionResult FooContent()
{
if (User.Identity.IsAuthenticated)
{
return PartialView("_auth");
}
else
{
return PartialView("_unauth");
}
}
Then call the action from within your view like this:
#Url.Action("FooContent", "Contoller");
This can also be used to check roles and return different partials.
Yes, views can have logic.
Here's an example of code that displays different content to the user depending on if they are logged in or not.
#if (User.Identity.IsAuthenticated)
{
<p>Welcome, #User.Identity.Name!</p>
}
else
{
<p>Welcome, anonymous!</p>
}
ASP.NET MVC allows users the ability to assign permissions to functionality (i.e. Actions) at Design Time like so.
[Authorize(Roles = "Administrator,ContentEditor")]
public ActionResult Foo()
{
return View();
}
To actually check the permission, one might use the following statement in a (Razor) view:
#if (User.IsInRole("ContentEditor"))
{
<div>This will be visible only to users in the ContentEditor role.</div>
}
The problem with this approach is that all permissions must be set up and assigned as attributes at design time. (Attributes are compiled in with the DLL so I am presently aware of no mechanism to apply attributes (to allow additional permissions) such as [Authorize(Roles = "Administrator,ContentEditor")] at runtime.
In our use case, the client needs to be able to change what users have what permissions after deployment.
For example, the client may wish to allow a user in the ContentEditor role to edit some content of a particular type. Perhaps a user was not allowed to edit lookup table values, but now the client wants to allow this without granting the user all the permissions in the next higher role. Instead, the client simply wants to modify the permissions available to the user's current role.
What options are strategies are available to allow permissions on MVC Controllers/Views/Actions to be defined outside of attributes (as in a database) and evaluated and applied at runtime?
If possible, we would very much like to stick as closely as we can to the ASP.NET Membership and Role Provider functionality so that we can continue to leverage the other benefits it provides.
Thank you in advance for any ideas or insights.
What options are strategies are available to allow permissions on MVC
Controllers/Views/Actions to be defined outside of attributes (as in a
database) and evaluated and applied at runtime?
A custom Authorize attribute is one possibility to achieve this:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
Roles = ... go ahead and fetch those roles dynamically from wherever they are stored
return base.AuthorizeCore(httpContext);
}
}
and then:
[MyAuthorize]
public ActionResult Foo()
{
return View();
}
As I'm lazy I couldn't be bothered rolling my own attribute and used FluentSecurity for this. In addition to the ability to apply rules at run time it allows a custom way to check role membership. In my case I have a configuration file setting for each role, and then I implement something like the following;
// Map application roles to configuration settings
private static readonly Dictionary<ApplicationRole, string>
RoleToConfigurationMapper = new Dictionary<ApplicationRole, string>
{
{ ApplicationRole.ExceptionLogViewer, "ExceptionLogViewerGroups" }
};
the application roles are then applied like so
SecurityConfigurator.Configure(
configuration =>
{
configuration.GetAuthenticationStatusFrom(() =>
HttpContext.Current.User.Identity.IsAuthenticated);
configuration.GetRolesFrom(() =>
GetApplicationRolesForPrincipal(HttpContext.Current.User));
configuration.ForAllControllers().DenyAnonymousAccess();
configuration.For<Areas.Administration.Controllers.LogViewerController>()
.RequireRole(ApplicationRole.ExceptionLogViewer);
});
filters.Add(new HandleSecurityAttribute());
and then the check is performed by
public static object[] GetApplicationRolesForPrincipal(IPrincipal principal)
{
if (principal == null)
{
return new object[0];
}
List<object> roles = new List<object>();
foreach (KeyValuePair<ApplicationRole, string> configurationMap in
RoleToConfigurationMapper)
{
string mappedRoles = (string)Properties.Settings.Default[configurationMap.Value];
if (string.IsNullOrEmpty(mappedRoles))
{
continue;
}
string[] individualRoles = mappedRoles.Split(',');
foreach (string indvidualRole in individualRoles)
{
if (!roles.Contains(configurationMap.Key) && principal.IsInRole(indvidualRole))
{
roles.Add(configurationMap.Key);
if (!roles.Contains(ApplicationRole.AnyAdministrationFunction))
{
roles.Add(ApplicationRole.AnyAdministrationFunction);
}
}
}
}
return roles.ToArray();
}
You could of course pull roles from a database. The nice thing about this is that I can apply different rules during development, plus someone has already done the hard work for me!
You could also consider doing task/activity based security and dynamically assign permission to perform those tasks to different groups
http://lostechies.com/derickbailey/2011/05/24/dont-do-role-based-authorization-checks-do-activity-based-checks/
You would need to mangle the provider a little bit to work with this but it is possible to stay inline with the .net authorisation
http://www.lhotka.net/weblog/PermissionbasedAuthorizationVsRolebasedAuthorization.aspx
If you need to do Method or Controller based authorization (deny access to the whole method or controller) then you can override OnAuthorization in the controller base and do your ouwn authorization. You can then build a table to lookup what permissions are assigned to that controller/method and go from there.
You can also do a custom global filter, which is very similar.
Another option, using your second approach, is to say something like this:
#if (User.IsInRole(Model.MethodRoles))
{
<div>This will be visible only to users in the ContentEditor role.</div>
}
And then in your controller populate MethodRoles with the roles assigned to that method.
Here's my model:
public class MyModel
{
public int BaseTypeField { set; get; }
public MyType UserTypeField { set; get; }
}
In the first action, i passed a MyModel to the view normally:
public ActionResult Action1()
{
MyModel model = new MyModel();
//do something with model.UserTypeField
return View(model);
}
In Action1View i can easily modify the model.BaseTypeField with HtmlHelper, but I dont wanna modify model.UserTypeField in this view(neither can i store it in HiddenFor).
Then Action1View submit the model to another action:
public ActionResult Action2(MyModel model)
{
//model.UserTypeField is lost here
return View();
}
Here comes the problem: how can i hold/save the model.UserTypeField except for something like Session??
Well, if you don't want to use session state, then your only option is to pass the information to the client and have him pass it back with his request. One way you could do this would be with a cookie. Another might be to use a hidden form field. You would include the field in your response to Action1, and the browser would automatically submit it in the request to Action2 (assuming you're using a form POST to call the action).
You have a number of options to preserve state across controller actions:
Store it in a Hidden input element in the View (though I appreciate that you say you can't, and there are plenty of good reasons why that might be the case).
Store it in Session State.
Store it in your application database (but then, you may as well use Session State).
Store it in a cookie. You can create a HttpCookie and add it to HttpContext.Current.Response.Cookies in Action1 and read it from HttpContext.Current.Request.Cookies in Action2.
If you only have a small amount of data and have no reason to use Session State elsewhere, I'd probably go for the cookie option. But Session State is there for precisely this kind of purpose. Don't be afraid to use it if it's the right thing.
Each action should have a parameter that has only properties for fields which you would like to accept from the request. The rest of the object should be loaded from the data store again. In other words, don't have Action2 take a property that takes in the whole model as it will allow your consumers to inadvertently alter more properties than they should be able to.
This may seem like a lot of work to do on every step, but you will save yourself many headaches by not having to do all of the validation for all the fields which you do not want changed. It is also easy to load the rest of the model from the data store if you wrap it up in a function.
TempData[] is intended to hold items between actions, but it does use the Session. If keys are not marked using Keep, then they are removed once the next Action is executed.
If you wanted to avoid Session fullstop, then you would have to serialize your object and send it to the client in the view (in a hidden form variable for example) and then deserialize it back into Action2.
If you wanted to use TempData (which would be simplest unless you can't use session for some reason), the syntax would just be:
public ActionResult Action1()
{
MyModel model = new MyModel();
//do something with model.UserTypeField
TempData["UserTypeField"] = model.UserTypeField;
return View(model);
}
public ActionResult Action2(MyModel model)
{
model.UserTypeField = TempData["UserTypeField"];
return View();
}
In my page i have used Session["user_id"] to know the status of user. Is there any page specific property to replace Session["user_id"].
Session is the best place to store user id!
You can wrap user id keeping using next extension method:
as far as Page.User returns IPrincipal then:
public static string UserId (this IPrincipal user)
{
get
{
return HttpContext.Current.State["user_id"];
}
set
{
HttpContext.Current.State["user_id"] = value;
}
}
Usage (inside a web page);
this.Request.User.UserId; // get, set
you can use a query string to store the user id instead of a session, moving it along from page to page.
another option is to use a viewstate, but this is limited per page and does not move along to other pages