Get the user details from MS Graph and show it in navbar - c#

I'm trying to show the current logged in user in navbar "Name , profile photo and phone number".
in Home controller i used this to get the details
public async Task<IActionResult> Profile()
{
try
{
var me = await _graphServiceClient.Me.Request().GetAsync();
ViewData["Me"] = me;
// Get user photo
using (var photoStream = await
_graphServiceClient.Me.Photo.Content.Request().GetAsync())
{
byte[] photoByte = ((MemoryStream)photoStream).ToArray();
ViewData["Photo"] = Convert.ToBase64String(photoByte);
}
}
catch (System.Exception)
{
ViewData["Photo"] = null;
}
return View();
}
Then pass the data to _loginPartial by ViewData, but when i navigate to another page the details on _loginPartial navbar disappear and only show when i call the home controller again
#var user = ViewData["me"] as Microsoft.Graph.User;
#if (User.Identity?.IsAuthenticated == true)
{
<li class="nav-item">
#if(user != null){
<span class="navbar-text text"> Hello #user.DisplayName!</span>
<span class="navbar-text text"> #user.BusinessPhones.FirstOrDefault()</span>
}
</li>
if (ViewData["photo"] != null)
{
<img style="margin: 5px 0; width: 50px; border-radius:50%;"
src="data:image/jpeg;base64, #ViewData["photo"]" />
}
else
{
<img style="margin: 5px 0; width: 50px; border-radius:50%;"
src="~/img/defaultprofileicon.jpg" />
}
Is there any way to get user details on Program.cs as i use .net core mvc 6 and share this details across the app so they will appear in navbar in every page, Thank for help

but when i navigate to another page the details on _loginPartial
navbar disappear
Below is a demo to use session to pass the string to appear in navbar in every page , you can refer to it.
1.Register IHttpContextAccessor and session in Program.cs
builder.Services.AddSession();
builder.Services.AddHttpContextAccessor();
...
app.UseSession();
2.In the controller, set a session variable:
HttpContext.Session.SetString("Photo", "p1");
3.2.In _loginPartial.cshtml, inject IHttpContextAccessor implementation and use it to get the HttpContext and Session object from that.
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor HttpContextAccessor
...
#HttpContextAccessor.HttpContext.Session.GetString("Photo")
result:

Related

How do I set up authentication & authorisation for ASP .NET Core 3.0 with PageModels?

I'm trying to set up an authentication and authorization section for a frontend web application I have. The application is set up as an ASP.NET Core Razor Page Application. (.NET Core 3.0); I also set it up with authentication pre-installed with the following command: dotnet new razor -au Individual.
With this, I'm trying to set up an OpenID Authentication External Login from an external service (Identity Server) I have. The problem lies with displaying the final user on the frontend (or if I'm doing this entirely wrong... - I've spent a questionable amount of time on this topic, and I couldn't find anything other than old questions, or content on how you could previously do it with MVC Apps) - I was also able to find the external user stored in the local database and can tell through code successfully signed in from the external Identity Server. (explained later)
This is my first project with a major authentication/authorization section - I apologize in advance If anything stupid jumps out. I'd love to find out what went wrong. Here goes!
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")
));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//options.DefaultAuthenticateScheme = IdentityConstants.ExternalScheme;
//options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.LoginPath = "/Account/Login/";
})
.AddOpenIdConnect(ChallengeScheme, o =>
{
o.ClientId = "client_id";
o.ClientSecret = "*******************";
o.Authority = "http://endpointhere.com";
o.ResponseType = "code" ;
o.SaveTokens = true;
o.Scope.Add("IdentityServerApi");
});
services.AddAuthorization();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
ExternalLogin.cshtml.cs ; OnGetCallbackAsync
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync("oidc", info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
// Store access token, token so it is included in cookie
var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
//await _signInManager.SignInAsync(user, props, info.LoginProvider);
// Update external authentication tokens with signInManager
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
LoginProvider = info.LoginProvider;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
_LoginPartial.cshtml
#using Microsoft.AspNetCore.Identity
#inject SignInManager<IdentityUser> SignInManager
#inject UserManager<IdentityUser> UserManager
<ul class="navbar-nav">
#if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<a class="nav-link text-light" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello #User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="#Url.Page("/", new { area = "" })" method="post" >
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-light" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
From the source code above: I was able to retrieve the access token I'd retrieved from the external identity server after the successful login - but I was unable to load any user information in the UI.
For instance, in the _LoginPartial, neither User.Identity.IsAuthenticated is ever true, or the default one from SignInManager.IsSignedIn(User).They were both always false.
I looked at the code below, and...:
// Code from ExternalLogin.cshtml.cs
var info = await _signInManager.GetExternalLoginInfoAsync();
info.Principal.Identity.IsAuthenticated always returned true; I also read up these resources to try and figure out what was going on - and I've tried a lot of things, to no avail. PS: I also went to try the old MVCs way - but I just could not do any scaffolding on the mac I own, so I made an issue on GitHub : https://github.com/aspnet/Identity/issues/1452
https://github.com/alexandre-spieser/AspNetCore.Identity.MongoDbCore/issues/8
https://github.com/aspnet/Security/issues/1538
https://github.com/aspnet/Identity/issues/1452
https://forums.asp.net/t/1177741.aspx?User+Identity+IsAuthenticated+remains+false+why+
https://github.com/IdentityServer/IdentityServer3.AspNetIdentity/issues/79
Now, I also understand that from this issue I know that User.Identity.IsAuthenticated is for anything else external, and SignInManager is solely for asp.net's identity framework. I'm only wondering if this has anything to do with my issue?
but I was unable to load any user information in the UI.
Note that when some user is authenticated and then be redirected to the URL of
/Identity/Account/ExternalLogin?returnUrl=%2F&handler=Callback
this Identity/Account/ExternalLogin Page will sign in the user using the scheme of Identity.Application by sending a cookie in following way:
set-cookie: .AspNetCore.Identity.Application={the-cookie-here}
In other words, the signin scheme here is IdentityConstants.ApplicationScheme (i.e. Identity.Application)
However, you've set the default authentication scheme as Cookies :
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
...
})
As a result, the User property within a RazorPage by default will always be authenticated with the scheme of CookieAuthenticationDefaults.AuthenticationScheme, which is different from the Identity.Application scheme. That's why your User.Identity.IsAuthenticated is always false.
To fix that issue, you could :
Approach 1: Configure a forward scheme for Cookies by :
.AddCookie(options =>{
options.LoginPath = "/Account/Login/";
options.ForwardDefault = IdentityConstants.ApplicationScheme;
})
Approach 2: use the IdentityConstants.ApplicationScheme as the default scheme
Approach 3: Add [Authorize(AuthenticationSchemes="Identity.Application")] manually for page handler/page model:
[Authorize(AuthenticationSchemes="Identity.Application")]

.NET Core Identity as UI canceling Register

I want to cancel the 'Register' option in a .NET Core 2.1 + Identity as UI application.
I can of course simply remove the button from the page, question is - is that safe ?
If not what are my other options ? should I use scaffolding to generate the Register code and then disable it there ?
(same goes for SetPassword etc)
Thanks
EDIT: seems like information regarding this was added here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-3.1&tabs=visual-studio#disable-register-page
Unfortunately the other two answers are incorrect - the question is actually referring to the new AddDefaultIdentity() extension which uses Razor pages to serve up a default UI. The answer that does address this will not remove the register functionality as requested in the question.
Background
AddDefaultIdentity works in a similar way to AddIdentity but also includes a call to AddDefaultUI which gives your app access to the new Identity razor views (currently 28 of them), these are in a new razor class library. Note that this is not the only difference between AddDefaultIdentity and AddIdentity (see later).
In order to change the default views you need to override ("scaffold") the views in your project and you can then amend them. If you do not override the views, or if you override them and then delete the cshtml files you will simply go back to the default UI versions! Even if you remove the links to e.g. register, the user can still navigate to the default register view if they guess the URL.
Option 1 - Override Views
If you want to keep some of the default views and amend or remove others, you can override views as follows (from this doc):
Right-click on your project > Add > New Scaffolded Item
From the left pane of the Add Scaffold dialog, select Identity > Add
In the Add Identity dialog, select the options you want
You can now either simply change the look and functionality of the view you have overridden, or to "remove" it you can have it return a 404 or redirect somewhere else. If you delete this overridden view the default UI will come back!
This approach can get messy quickly if you want to override all of the views.
Option 2 - Don't Add Default UI
Another option is to go back to the old way of adding identity which does not make a call to AddDefaultUI, the downside is that you will need to add all views yourself. You can do this as follows (from this doc - although ignore the first line about overriding all views, that applies to option 1 above):
//remove this: services.AddDefaultIdentity<IdentityUser>()
//use this instead to get the Identity basics without any default UI:
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//this assumes you want to continue using razor views for your identity UI
//it specifies areas can be used with razor pages and then adds an
//authorize filter with a default policy for the folder /Account/Manage and
//the page /Account/Logout.cshtml (both of which live in Areas/Identity/Pages)
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
});
//configures the application cookie to redirect on challenge, etc.
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
//configures an email sender for e.g. password resets
services.AddSingleton<IEmailSender, EmailSender>();
Note that I'm not 100% convinced this second approach is without problems either, as mentioned above there are other differences between AddDefaultIdentity and AddIdentity. For example the latter adds the RoleManager service whereas the former does not. Also, it's unclear to me if both of these approaches will be supported and maintained equally going forward.
If in doubt about what the above options are doing (and if you have a few hours to kill) you can look at the source for AddDefaultIdentity (which also calls AddIdentityCookies and AddIdentityCore) compared to the older AddIdentity.
Option 3 - Hybrid Approach
The best option currently is probably to combine the previous 2, in the following way:
Set up your project to use default identity
Scaffold just the views you want to include and edit them accordingly
Switch to the old AddIdentity call and include the razor options as shown in option 2 (adjusting as necessary depending on which views you've included
You now have just the views you want and they are based on the default implementations meaning most of the work is done for you for these views.
For ASP.NET Web Pages, this is an add on for the answer earlier to include ASP.Net razor Web Pages. I have separated these as if someone was to need them and not to get confused with each other. Web Pages is different as it includes code behind as web forms did.
First You will edit the Pages > _LoginPartial.cshtml
Remove line <li><a asp-page="/Account/Register">Register</a></li>
Next Edit Pages > Account > Login.cshtml.
Remove the following:
<div class="form-group">
<p>
<a asp-page="./ForgotPassword">Forgot your password?</a>
</p>
<p>
<a asp-page="./Register" asp-route-returnUrl="#Model.ReturnUrl">Register as a new user</a>
</p>
</div>
Also remove:
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to log in.</h4>
<hr />
#{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this article
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form asp-page="./ExternalLogin" asp-route-returnUrl="#Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
#foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-default" name="provider" value="#provider.Name" title="Log in using your #provider.DisplayName account">#provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
Now edit the code behind Login.cshtml.cs
Remove:
public IList<AuthenticationScheme> ExternalLogins { get; set; }
Also Remove:
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
Edit Pages > Account > Manage > _ManageNav.cshtml
Remove:
#if (hasExternalLogins)
{
<li class="#ManageNavPages.ExternalLoginsNavClass(ViewContext)"><a asp-page="./ExternalLogins">External logins</a></li>
}
Next we will remove the following files from the Pages > Account directory:
ExternalLogin.cshtml
ForgotPassword.cshtml
ForgotPasswordConfirmation.cshtml
Register.cshtml
ResetPassword.cshtml
ResetPasswordConfirmation.cshtml
Remove the following files from the Pages > Account > Manage directory:
ExternalLogin.cshtml
I had a similar problem but I wanted to disable the entire /Identity/Account pages and I added the following code in my configure of the Startup.cs file.
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.ToLower().StartsWith("/identity/account"))
{
context.Response.StatusCode = 404; //Not found
return;
}
await next();
});
For your problem you can just disable the registration form using the following code that I have not tested but I guess that it is going to work.
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.ToLower().StartsWith("/identity/account/register"))
{
context.Response.StatusCode = 404; //Not found
return;
}
await next();
});
This code checks the request path and in case it starts with /identity/account/register it returns the status code 404 Not Found
This is the official docs way of doing this.
Disable user registration
Taken from the docs:
Scaffold Identity. Include Account.Register, Account.Login, and
Account.RegisterConfirmation
dotnet aspnet-codegenerator identity -dc RPauth.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.RegisterConfirmation"
Update Areas/Identity/Pages/Account/Register.cshtml.cs so users can't
register from this endpoint:
public class RegisterModel : PageModel
{
public IActionResult OnGet()
{
return RedirectToPage("Login");
}
public IActionResult OnPost()
{
return RedirectToPage("Login");
}
}
Update Areas/Identity/Pages/Account/Register.cshtml to be consistent
with the preceding changes:
#page
#model RegisterModel
#{
ViewData["Title"] = "Go to Login";
}
<h1>#ViewData["Title"]</h1>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
Comment out or remove the registration link from
Areas/Identity/Pages/Account/Login.cshtml
Update the Areas/Identity/Pages/Account/RegisterConfirmation page.
Remove the code and links from the cshtml file.
Remove the confirmation code from the PageModel:
[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
public IActionResult OnGet()
{
return Page();
}
}
NOTE: This will also add the default identity db context to your project. If you already have a db context, make sure to remove the one added by the scaffolding.
I am assuming you are talking about a Model-View-Controller web application. I can tell you it is not safe to just remove the button or even the views for such.
I am also assuming you would like to remove 3rd party login that would also create a registered user.
I would do the following:
In your account controller remove the following
[HttpGet]
[AllowAnonymous]
public IActionResult Register(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Also in the Account Controller further down remove the following:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToAction(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToAction(nameof(Lockout));
}
else
{
// If the user does not have an account, then ask the user to create an account.
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = info.LoginProvider;
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
}
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
throw new ApplicationException("Error loading external login information during confirmation.");
}
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
}
AddErrors(result);
}
ViewData["ReturnUrl"] = returnUrl;
return View(nameof(ExternalLogin), model);
}
also remove
[HttpGet]
[AllowAnonymous]
public IActionResult ForgotPassword()
{
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
{
// Don't reveal that the user does not exist or is not confirmed
return RedirectToAction(nameof(ForgotPasswordConfirmation));
}
// For more information on how to enable account confirmation and password reset please
// visit https://go.microsoft.com/fwlink/?LinkID=532713
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "Reset Password",
$"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
return RedirectToAction(nameof(ForgotPasswordConfirmation));
}
// If we got this far, something failed, redisplay form
return View(model);
}
[HttpGet]
[AllowAnonymous]
public IActionResult ForgotPasswordConfirmation()
{
return View();
}
[HttpGet]
[AllowAnonymous]
public IActionResult ResetPassword(string code = null)
{
if (code == null)
{
throw new ApplicationException("A code must be supplied for password reset.");
}
var model = new ResetPasswordViewModel { Code = code };
return View(model);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction(nameof(ResetPasswordConfirmation));
}
var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction(nameof(ResetPasswordConfirmation));
}
AddErrors(result);
return View();
}
[HttpGet]
[AllowAnonymous]
public IActionResult ResetPasswordConfirmation()
{
return View();
}
Now under Models you can delete the following files:
ExternalLoginViewModel
ForgotPasswordViewModel
RegisterViewModel
ResetPasswordViewModel
Under Views i would remove:
ConfirmEmail
ExternalLogin
ForgotPassword
ForgotPasswordConfirmation
Register
ResetPassword
ResetPasswordConfirmation
Also under Account views edit Login.cshtml and remove the following:
<div class="form-group">
<p>
<a asp-page="./ForgotPassword">Forgot your password?</a>
</p>
<p>
<a asp-page="./Register" asp-route-returnUrl="#Model.ReturnUrl">Register as a new user</a>
</p>
</div>
Also remove:
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to log in.</h4>
<hr />
#{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this article
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form asp-page="./ExternalLogin" asp-route-returnUrl="#Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
#foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-default" name="provider" value="#provider.Name" title="Log in using your #provider.DisplayName account">#provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
Now under your Shared Views open _LoginPartial.cshtml and delete the following:
<li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
Under Manage Views _ManageNav.cshtml delete the following:
#if (hasExternalLogins)
{
<li class="#ManageNavPages.ExternalLoginsNavClass(ViewContext)"><a asp-action="ExternalLogins">External logins</a></li>
}
Now even if you go to the URL yourapp.com/Account/Register you will get a 404 page.
Hope this helps.

MVC, how to post data to controller and redirect to aspx page

I have an MVC view where user can set a flag and post data to controller (post because I want to hidden query string)
After the controller have done his job I want to redirect to website home page that is an aspx page (my site is mixed aspx and MVC)
Is there a way to do that?
This is my view
#model MessaggiVM
<form role="form" class="form-inline" method="post" action="Messaggi/VaiAllaHome">
<button id="btnHome">Vai alla pagina iniziale</button>
<div class="form-group">
<label for="nascondi">hiding</label>
<input id="nascondi" type="checkbox" name="nascondi" value="true" />
</div>
<input type="hidden" name="elencoPost" value="#Model.Posts" />
#*#Html.ActionLink("Messaggi", "VaiAllaHome", new { posts = Model.Posts} )*#
</form>
And this the controller
[HttpPost]
public RedirectResult VaiAllaHome(bool? nascondi = false, IEnumerable<Messaggio> elencoPost = null)
{
// do something
return Redirect(Url.Content("~/"));
}
When I run this code controller action is executed without error but redirect is not done and browser remain on the view
Other problem is that elencoPost parameter is empty in the action but I'm investigating it
EDIT
Honestly I'm thinking to post data on input change and switch button for a simply link
EDIT 2:
found the reason: in default.aspx i have a auto-redirect to Message page :(
Try
return Redirect("~/home.aspx");
or
return Redirect(Url.Content("~/home.aspx")
You should be able to use Redirect with a relative url:
[HttpPost]
public RedirectResult VaiAllaHome(bool? nascondi = false, IEnumerable<Messaggio> elencoPost = null)
{
// do something
return Redirect("/home.aspx");
}
Try using a #Url.Content on your form tag
<form action="#Url.Content("~/Messaggi/VaiAllaHome/")">
Then in your Controller
[HttpPost]
public RedirectResult VaiAllaHome(bool? nascondi = false, IEnumerable<Messaggio> elencoPost = null)
{
// do something
return View(Url.Content("~/"));
//return RedirectToAction("Action", "Controller", new { routeParameter = value } /*e.g. "id = 1"*/);
}

DropDownList on mvc filters a display of cards/boxes, but I can't display all the cards on load or with default selection

I have a Partial View that can currently display three cards/boxes that contain snippets of information about different case studies. I want this page to on load, display all the cards, but then also have a dropdown menu that filters each card by their CaseStudyIndustry property and be able to go back to displaying all the cards when you select "Select an industry"/the default selection.
I got a lot of help from http://weblogs.thinktecture.com/cnagel/2011/06/filter-and-display-data-with-aspnet-mvc-part-2partial-views-and-jquery.html, but I can't seem to get all the cards to display onload or when selecting the default DropDownList item. I've been stuck on this for a few weeks.
Here is my code:
Partial View:
#model IEnumerable<KenCast.Models.CaseStudies>
#foreach (var item in Model)
{
await Html.RenderPartialAsync("_CaseStudyCard.cshtml", item);
}
Index/page with the dropdown menu:
<h1>#ViewData["Title"]</h1>
<fieldset>
<div>
#Html.DropDownList("Industries", "Select an industry")
</div>
<br />
<div id="target">
</div>
<div id="log">
</div>
</fieldset>
<h3>For development only</h3>
<p>This link below to create new application is for development only. Will change once Identity is added to this app.</p>
<p>
<a asp-action="Create">Create New</a>
</p>
Script from: http://weblogs.thinktecture.com/cnagel/2011/06/filter-and-display-data-with-aspnet-mvc-part-2partial-views-and-jquery.html
$(document).ready(function () {
$("#Industries").change(function () {
$("#log").ajaxError(function (event, jqxhr, settings, exception) {
alert(exception);
});
// loads all cards from all industries
// Changes cards by industry when selected from dropdown menu
var industrySelected = $("select option:selected").text();
$.get('#Url.Action("CaseStudiesByIndustryPartial")', { id: industrySelected }, function (data) {
$("#target").html(data);
});
});
});
Controller:
public IActionResult Index()
{
var industries = new SelectList(_context.CaseStudies.Select(c => c.CaseStudyIndustry).Distinct().ToList());
ViewBag.Industries = industries;
return View();
}
public PartialViewResult CaseStudiesByIndustryPartial(string id)
{
return PartialView(
_context.CaseStudies.Where(c => c.CaseStudyIndustry == id).OrderBy(c => c.CaseStudyDate)
.ThenBy(c => c.CaseStudyTitle).ToList());
}
Any help would be greatly appreciated. Thank you!!
Figured this out with the hints from Stephen Muecke. The value of the first item selected in the dropbox was "Select an industry", so I used an if statement to change the displayed cards when "Select an industry was selected.
Code within the Controller now looks like this:
//Responsible for filtering case studies when item in DropDownList is selected.
public PartialViewResult CaseStudiesByIndustryPartial(string id)
{
if (id == "Select an industry") return PartialView(
_context.CaseStudies.OrderByDescending(c => c.CaseStudyDate)
.ThenBy(c => c.CaseStudyTitle).ToList());
else return PartialView(
_context.CaseStudies.Where(c => c.CaseStudyIndustry == id).OrderByDescending(c => c.CaseStudyDate)
.ThenBy(c => c.CaseStudyTitle).ToList());
}
Not sure if this is the most elegant way to do this, but it works! If anyone has another suggestion for how to write this without repeating code, please let me know!
The code in the controller, changed the cards in the view, but would only display all of the cards after I had selected another industry and then selected "Select an industry".
I ended up creating a ViewComponent that loaded all of the cards when the page loaded. The ViewComponent ended up looking like this:
namespace KenCast.ViewComponents
{
public class CaseStudiesViewComponent : ViewComponent
{
//allows for data access
private readonly ApplicationDbContext _context;
public CaseStudiesViewComponent(ApplicationDbContext c)
{
_context = c;
}
public Task<IViewComponentResult> InvokeAsync()
{
var casestudies = (from cs in _context.CaseStudies.ToList()
.OrderByDescending(cs => cs.CaseStudyDate)
.ThenBy(cs => cs.CaseStudyTitle)
select cs).ToList();
return Task.FromResult<IViewComponentResult>(View(casestudies));
}
}
}
I called the ViewComponent in the index page (page with the dropdown menu). The index page now looks like this:
<h1>#ViewData["Title"]</h1>
<fieldset>
<div>
#Html.DropDownList("Industries", "Select an industry")
</div>
<br />
<div id="target"> #* Loads data from CaseStudiesViewComponent *#
#await Component.InvokeAsync("CaseStudies")
</div>
<div id="log">
</div>
</fieldset>
<h3>For development only</h3>
<p>This link below to create new application is for development only. Will change once Identity is added to this app.</p>
<p>
<a asp-action="Create">Create New</a>
</p>
#* Script from: http://weblogs.thinktecture.com/cnagel/2011/06/filter-and-display-data-with-aspnet-mvc-part-2partial-views-and-jquery.html *#
<script type="text/javascript">
$(document).ready(function () {
$("#Industries").change(function () {
$("#log").ajaxError(function (event, jqxhr, settings, exception) {
alert(exception);
});
//loads all cards from all industries
//Changes cards by industry when selected from dropdown menu
var industrySelected = $("select option:selected").text();
$.get('#Url.Action("CaseStudiesByIndustryPartial")',
{ id: industrySelected }, function (data) {
$("#target").html(data);
});
});
});
</script>
Hope this helps if anyone else runs into a similar issue.

Using Html.Action causes query string too long error

I have a _LayoutView:
#{
Layout = "~/Views/Shared/_NavLayout.cshtml";
}
#section styles
{
#RenderSection("styles", required: false)
}
<div class="container" style="padding-top: 60px;">
<div class="row">
<div class="col-md-12">
#Html.Action("AccountNavigation")
</div>
</div>
#RenderSection("InlineTitle", required:false)
#RenderBody()
</div>
#section scripts {
#Scripts.Render("~/bundles/jqueryval")
#RenderSection("scripts", required: false)
}
That renders fine if I remove the
#Html.Action("AccountNavigation")
Otherwise I get:
The action method is:
[ChildActionOnly]
public ActionResult AccountNavigation()
{
var roles = UserManager.GetRoles(User.Identity.GetUserId());
ViewBag.IsAdmin = roles.Contains("Admin");
return PartialView("_AccountNavigatorPartial");
}
And Ive tried stripping it back to just:
[ChildActionOnly]
public ActionResult AccountNavigation()
{
ViewBag.IsAdmin = false;
return null;
}
But it makes no difference.
One of the child views that uses the layout is Login.
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.OidLoginFailed = false;
ViewBag.ReturnUrl = returnUrl;
return View();
}
If I put a break piont in there I can see its being called multiple times per request and building up the ReturnUrl until it fails hence the error message. This is why I stripped back the AccountNavigation ActionMethod.
I thought maybe an anon request was causing a post back via some config setting that says if Anon redirect to Login and round and round it would go but I cant see where that is being triggered.
The account _AccountNavigatorPartial is just:
<ul class="nav navbar-nav navbar-Left">
<li>#Html.ActionLink("Manage Credentials", "Credentials", "Account",
routeValues: null, htmlAttributes: new { id = "credentialsLink" })</li>
<li>#Html.ActionLink("Manage Profile", "Profile", "Account",
routeValues: null, htmlAttributes: new { id = "credentialsLink" })</li>
</ul>
So all I'm trying to do is inject some html for account navigation. I'm using ASP.Identity for membership but I cant see how that makes any difference as I'm requesting an Anon accessible page.
It's hard to tell without seeing the controller and knowing how you are handling authorization, however, since your login page is using that Layout, you may be experiencing a circular call due to failure of authorization on you child action.
Have you tried adding the attribute [AllowAnonymous] to your child action?
We were getting similar UriFormatException: Invalid URI: The Uri string is too long. - we found the solution was to pass the model in as an explicit parameter instead of just the typical approach.
Not Working
#Html.Action("MyChildAction", "Path", Model)
Working
#Html.Action("MyChildAction", "Path", new { viewModel = Model} )
This seems to be triggered by using a very large view model - this must wrap it and bypass the encoding issue UriHelper.EscapeString which overflows the buffer.

Categories