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>
}
Related
I am migrating an ASP.NET application to ASP.NET Core and they have some calls to HttpServerUtility.Transfer(string path). However, HttpServerUtility does not exist in ASP.NET Core.
Is there an alternative that I can use? Or is Response.Redirect the only option I have?
I want to maintain the same behaviour as the old application as much as possible since there is a difference in between Server.Transfer and Response.Redirect.
I see some options for you, depending on your case:
Returning another View: So just the HTML. See answer of Muqeet Khan
Returning another method of the same controller: This allows also the execution of the business logic of the other action. Just write something like return MyOtherAction("foo", "bar").
Returning an action of another controller: See the answer of Ron C. I am a bit in troubles with this solution since it omits the whole middleware which contains like 90% of the logic of ASP.NET Core (like security, cookies, compression, ...).
Routing style middleware: Adding a middleware similar to what routing does. In this case your decision logic needs to be evaluated there.
Late re-running of the middleware stack: You essentially need to re-run a big part of the stack. I believe it is possible, but have not seen a solution yet. I have seen a presentation of Damian Edwards (PM for ASP.NET Core) where he hosted ASP.NET Core without Kestrel/TCPIP usage just for rendering HTML locally in a browser. That you could do. But that is a lot of overload.
A word of advice: Transfer is dead ;). Differences like that is the reason for ASP.NET Core existence and performance improvements. That is bad for migration but good for the overall platform.
You are correct. Server.Transfer and Server.Redirect are quite different. Server.Transfer executes a new page and returns it's results to the browser but does not inform the browser that it returned a different page. So in such a case the browser url will show the original url requested but the contents will come from some other page. This is quite different than doing a Server.Redirect which will instruct the browser to request the new page. In such a case the url displayed in the browser will change to show the new url.
To do the equivalent of a Server.Transfer in Asp.Net Core, you need to update the Request.Path and Request.QueryString properties to point to the url you want to transfer to and you need to instantiate the controller that handles that url and call it's action method. I have provided full code below to illustrate this.
page1.html
<html>
<body>
<h1>Page 1</h1>
</body>
</html>
page2.html
<html>
<body>
<h1>Page 2</h1>
</body>
</html>
ExampleTransferController.cs
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace App.Web.Controllers {
public class ExampleTransferController: Controller {
public ExampleTransferController() {
}
[Route("/example-transfer/page1")]
public IActionResult Page1() {
bool condition = true;
if(condition) {
//Store the original url in the HttpContext items
//so that it's available to the app.
string originalUrl = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}{HttpContext.Request.Path}{HttpContext.Request.QueryString}";
HttpContext.Items.Add("OriginalUrl", originalUrl);
//Modify the request to indicate the url we want to transfer to
string newPath = "/example-transfer/page2";
string newQueryString = "";
HttpContext.Request.Path = newPath;
HttpContext.Request.QueryString = new QueryString(newQueryString);
//Now call the action method for that new url
//Note that instantiating the controller for the new action method
//isn't necessary if the action method is on the same controller as
//the action method for the original request but
//I do it here just for illustration since often the action method you
//may want to call will be on a different controller.
var controller = new ExampleTransferController();
controller.ControllerContext = new ControllerContext(this.ControllerContext);
return controller.Page2();
}
return View();
}
[Route("/example-transfer/page2")]
public IActionResult Page2() {
string originalUrl = HttpContext.Items["OriginalUrl"] as string;
bool requestWasTransfered = (originalUrl != null);
return View();
}
}
}
Placing the original url in HttpContext.Items["OriginalUrl"] isn't strictly necessary but doing so makes it easy for the end page to know if it's responding to a transfer and if so what the original url was.
I can see this is a fairly old thread. I don't know when URL Rewriting was added to .Net Core but the answer is to rewrite the URL in the middleware, it's not a redirect, does not return to the server, does not change the url in the browser address bar, but does change the route.
resources:
https://weblog.west-wind.com/posts/2020/Mar/13/Back-to-Basics-Rewriting-a-URL-in-ASPNET-Core
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting?view=aspnetcore-5.0
I believe you are looking for a "named view" return in MVC. Like so,
[HttpPost]
public ActionResult Index(string Name)
{
ViewBag.Message = "Some message";
//Like Server.Transfer() in Asp.Net WebForm
return View("MyIndex");
}
The above will return that particular view. If you have a condition that governs the view details you can do that too.
I know that this is a very old question, but if someone uses Razor Pages and is looking to a Server.Transfer alternative (or a way to return a different view depending on a business rule), you can use partial views.
In this example, my viewmodel has a property called "UseAlternateView":
public class TestModel : PageModel
{
public bool UseAlternateView { get; set; }
public void OnGet()
{
// Here goes code that can set UseAlternateView=true in certain conditions
}
}
In my Razor View, I renderize a diferent partial view depending of the value of the UseAlternateView property:
#model MyProject.Pages.TestModel
#if (Model.UseAlternateView)
{
await Html.RenderPartialAsync("_View1", Model);
}
else
{
await Html.RenderPartialAsync("_View2", Model);
}
The partial views (files "_View1.cshtml" and "_View2.cshtml"), contain code like this:
#model MyProject.Pages.TestModel
<div>
Here goes page content, including forms with binding to Model properties
when necessary
</div>
Obs.: when using partial views like this, you cannot use #Region, so you may need to look for an anternative for inserting scripts and styles in the correct place on the master page.
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);
}
I am converting a webapp from a frameset layout to Masterpage layout. Each of the pages within the frameset use a common custom class to aquire the logged in user, this custom class inherits from Page.
I thought that an interface would be the proper way to convert this however I was really wrong. What is the best and most efficient means to store a global variable or class so that each content page for the Masterpage can reference the logged in user class?
Make it a simple static class with static method that will return logged user class, there is no need to tie this code with ASP.NET page. If getting the logged on user requires some time vise expensive operations (like DB query for example) store logged user class instance in the session. Something like this :
public static class Users
{
public static User GetLoggedUser()
{
if (HttpContext.Current.Session["LoggedUser"] != null)
{
return (User)HttpContext.Current.Session["LoggedUser"];
}
else
{
User usr = GetLoggedUserSomehow(); // here goes your current code to get user
HttpContext.Current.Session["LoggedUser"] = usr;
return usr;
}
}
}
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.
While looking into forms authorizing/authentication, I found that it is possible to do role based authorizing by adding an array of roles to a FormsAuthenticationTicket. That way I can write
User.IsInRole(role from database)
But is there any way to do the same thing with permissions on a role like :
if (User.IsInRole(role from database)) {
if (User.CanRead()) {
//--- Let the user read
}
if (User.CanWrite()) {
//--- Let the user write
}
}
I have read a couple of articles and forum post's where permission is added to the array instead of the roles, resulting in using
User.IsInRole(permission from database)
However that's not the same thing. Hope someone can give some input on this matter, throw a link to an article or better yet, an code sample.
You're better off changing the way you think about a role. Use the term "permission" or "claim" if that helps. Then create all the roles you need and link a given user to all the necessary roles.
One user can belong to multiple roles. This way, the following simple code will work fine and you don't need to build your own unique way of how things work.
if(User.IsInRole(someRole) && User.IsInRole(someOtherRole))
{
// do something
}
You can make some C# extension methods to make this more readable too:
if(User.IsInSomeRoleAndOtherRole())
{
// do something
}
The extension methods can look something like the following. Create a new class with the following code, then include the class namespace in your code, and you can use the extension method as shown above.
using System.Security.Principal;
namespace MyCompany
{
public static class MyExtensions
{
public static bool IsInSomeRoleAndOtherRole(this IPrincipal principal)
{
if (!principal.IsInRole("someRole"))
return false;
if (!principal.IsInRole("someOtherRole"))
return false;
return true; // the user meets the requirements
}
}
}