_layout.cshtml not seeing object when at /Home/Index - asp.net-core-mvc

I am writing an ASP.NET Core 6 MVC app.
I have an object of type UserInfo that is set (not important where).
Under Shared, my _Layout.cshtml shows the "Login ID" at the top in a menu bar.
The portion that shows this user name is in a _LogInOutPartial.cshtml
in the the partial view, I inspect the UserInfo object and set a variable displayUser accordingly: if the user object is null, I set the variable to "Guest", otherwise I pull the login id into this variable.
Code in _LogInOutPartial.cshtml is:
#model SisExtMvcAppSSO.Models.UserInfo;
#{
bool isGuest = (Model == null || !Model.IsLogged);
bool isLoggedIn = !isGuest;
string displayUser = (isGuest ? "Guest" : Model.LoginId);
}
<button class="btn btn-outline-success my-2 my-sm-0">
<i class="bi bi-person-badge"></i> #displayUser
</button>
When the object is set, if I go to some controller/action, e.g. https://some.com/myApp/Home/Claims, the login id shows fine (e.g. "mcollins") in the top menu bar.
However, if I go to https://some.com/myApp/ (or https://some.com/myApp/Home/Index or https://some.com/myApp/Home), the menu bar (_layout.cshtml) will not show the login id, instead it shows "Guest"
This is even when the /Home/Index will show the login id in its view!
The problem is, apparently, the object it is not seen in _Layout.cshtml.
But then how come it is seen in the menu bar (_Layout.cshtml) when I go to /Home/Claims?

Usually,we will use UserManager as the model in _LogInOutPartial.cshtml for Identity.If you don't want to use it,you can try to use Session and add the object to session in somewhere.Once the data is added,you can get it in other views.
Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromDays(1);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
...
app.UseAuthorization();
app.UseSession();
...
partial view:
#using Microsoft.AspNetCore.Http;
#using Newtonsoft.Json;
#{
UserInfo userInfo=null;
if (!string.IsNullOrEmpty(Context.Session.GetString("userInfo")))
{
userInfo = JsonConvert.DeserializeObject<UserInfo>(Context.Session.GetString("userInfo"));
}
bool isGuest = (userInfo == null || !userInfo.IsLogged);
bool isLoggedIn = !isGuest;
string displayUser = (isGuest ? "Guest" : userInfo.LoginId);
}
<button class="btn btn-outline-success my-2 my-sm-0">
<i class="bi bi-person-badge"></i> #displayUser
</button>
Action:
public IActionResult YourAction()
{
UserInfo userInfo = new UserInfo { LoginId = "mcollins", IsLogged = true };
HttpContext.Session.SetString("userInfo", JsonConvert.SerializeObject(userInfo));
return View();
}

Related

How to use ajax link instead of submit button for form?

I have Ajax Form in my view:
#using (Ajax.BeginForm("SearchHuman", "Search", new AjaxOptions(){
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "result" }))
{
<div class="editor-field">
#DescriptionStrings.Lastname:
#Html.TextBox("LastName")
</div>
<div class="editor-field">
#DescriptionStrings.Firstname:
#Html.TextBox("Name")
</div>
//submit button
<input type="submit" value='Start Searching' />
//submit link
#Ajax.ActionLink("search", "OtherSearch", new{lastName ="",...}, new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "tab"
})
}
I want to have submit button and the link for 2 different searches (in different databases) using only one form. But how to pass route values from the textboxes of the form into Ajax.ActionLink?
Thanks in advance!
But how to pass route values from the textboxes of the form into Ajax.ActionLink?
You can't. You should use a submit button if you want to send the values to the server. You could have 2 submit buttons in the same form which both submit to the same controller action. Then inside this action you can test which button was clicked and based on its value perform one or the other search.
Example:
<button type="submit" name="btn" value="search1">Start Searching</button>
<button type="submit" name="btn" value="search2">Some other search</button>
and then inside your controller action:
[HttpPost]
public ActionResult SomeAction(string btn, MyViewModel model)
{
if (btn == "search1")
{
// the first search button was clicked
}
else if (btn == "search2")
{
// the second search button was clicked
}
...
}
The solution we opted for was to implement a custom ActionMethodSelectorAttribute which allowed us to differentiate which button was pressed based on its name property. We then decorated many methods with the ActionName decorator giving them all the same action name (the one specified in the BeginFrom helper), and then we used our custom ActionMethodSelector decorator to differentiate which method is to be called based on the name of the button clicked. The net result is that each submit button leads to a separate method being called.
Some code to illustrate:
In controller:
[ActionName("RequestSubmit")]
[MyctionSelector(name = "Btn_First")]
public ActionResult FirstMethod(MyModel modelToAdd)
{
//Do whatever FirstMethod is supposed to do here
}
[ActionName("RequestSubmit")]
[MyctionSelector(name = "Btn_Second")]
public ActionResult SecondMethod(MyModel modelToAdd)
{
//Do whatever SecondMethod is supposed to do here
}
In view:
#using (Ajax.BeginForm("RequestSubmit",.....
<input type="submit" id="Btn_First" name="Btn_First" value="First"/>
<input type="submit" id="Btn_Second" name="Btn_Second" value="Second"/>
As for the custom attribute:
public string name { get; set; }
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var btnName = controllerContext.Controller.ValueProvider.GetValue(name);
return btnName != null;
}

MVC3 Parent Child Views

I want to create a layout like the User page on Stack Overflow where there is a view (the parent view) at the top of the page and then content in tabs, each with it's own view (child views).
When I hover over each of the tabs on the User page in SO it looks like they are pointed at the user controller and are being sent the tab name in the query string to render the appropriate tab content.
I believe I can achieve this using a layout with a section defined in the parent view. The section would be the child view, but I don't know how I would tell the section which view or partial view to show.
I have not been able to find anything useful on the web. Can someone tell me how to do this or at least point me in the right direction?
Thanks in advance!
Edit: Thanks to #Mystere's help I was able to come up with the solution below in case anyone else is trying to do the same thing.
HTH
Final Solution:
Controller Actions
public ActionResult Details(int id, string tab = null)
{
ViewBag.Jobid = id;
ViewBag.Tab = tab ?? "Services";
var viewModel = getJobRecordDetails(id);
return View(viewModel);
}
public ActionResult JobInfo(int id, string tab)
{
ViewBag.Jobid = id;
ViewBag.Tab = tab;
if (tab == "Services")
{
var viewModel = getServices(id);
return View("Services", viewModel);
}
if (tab == "Equipment")
{
var viewModel = getEquipment(id);
return View("Equipment", viewModel);
}
if (tab == "Personnel")
{
var viewModel = getPersonnel(id);
return View("Personnel", viewModel);
}
return View("Error");
}
Parent View
#model MyApplication.Models.JobViewModel
#{
ViewBag.Title = "Details";
}
<h2>Job Details</h2>
...
#* Child View Action *#
#Html.Action("JobInfo", new { id = ViewBag.Jobid, tab = ViewBag.Tab })
Child View
#model MyApplication.Models.ServicesViewModel[]
#{
ViewBag.Title = "Services";
Layout = null;
}
#* Submenu Navigation *#
#{
Html.RenderPartial("SubMenu");
}
<h2>Services</h2>
Services here...
Subnavigation Partial View
<div id="submenucontainer">
<ul id="submenu">
<li class="#Html.ActiveTab("Job","JobInfo","Services")">Services </li>
<li class="#Html.ActiveTab("Job","JobInfo","Equipment")">Equipment</li>
<li class="#Html.ActiveTab("Job","JobInfo","Personnel")">Personnel</li>
</ul>
ActiveTab Helper
public static string ActiveTab(this HtmlHelper helper, string controller, string action, string tab)
{
var classValue = "";
var currentController =
helper.ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString();
var currentAction =
helper.ViewContext.Controller.ValueProvider.GetValue("action").RawValue.ToString();
var currentTab = helper.ViewContext.Controller.ValueProvider.GetValue("tab").RawValue.ToString();
if (currentController == controller && currentAction == action && currentTab == tab)
classValue = "selected";
return classValue;
}
It is unlikely they are using a section for that. sections are used primarily in layout pages (the equivelent of master pages).
More than likely, they just have multiple views, and they pass whichever view is appropriate to the View() method. They might use partial views, or MVC templates to render the tab areas, so that common code is factored out.
Edit:
As requested, code sample:
In action method:
public ActionResult Dashboard(string tab) {
if (tab == "summary")
ViewBag.Tab = "~/Views/Dashboard/Summary.cshtml";
if (tab == "activity")
ViewBag.Tab = "~/Views/Dashboard/Activity.cshtml";
return View()
}
in Dashboard.cshmtl
... your parent view
#Html.Partial(ViewBag.Tab)
... your footer
It's not rocket science. There are so many ways to do this it doesn't take much thought to come up with one of them.

Many Buttons on a Page, Need to send back Unique Post Data with each

I'm listing out a bunch of cars with a button next to them that when clicked will need to perform a GET but also sends over that item's model.Name:
#using (Html.BeginForm("GetCarUrl", "Car", FormMethod.Get, new { model = Model }))
{
if(Model.Cars != null && Model.Cars.Count > 0)
{
foreach (CarContent car in Model.Cars)
{
<p>#car.Name</p>
}
<input type="button" value="Get Car Url" class="submit" />
}
So the page renders a bunch of hyperlinks and buttons:
[hyperlink1] [submit]
[hyperlink2] [submit]
[hyperlink3] [submit]
[hyperlink4] [submit]
[hyperlink5] [submit]
...
When a user clicks on any of the submits, I need to pass back its corresponding #car.CarType for that specific hyperlink
Not sure how to go about this. My action method expects a #car.CarType for that specific car hyperlink to be sent to it
UPDATE
Here is my markup:
Html.Hidden("userId", Model.UserId);
Html.Hidden("lessonId", Model.LessonId);
#if(....)
{
foreach (Car car in Model.Cars)
{
using (Html.BeginForm("GetFileDownloadUrl", "Car", FormMethod.Get, new { carFileUrl= car.CarFileUrl}))
{
<p>#fileContent.Name
<input type="submit" value="download" name="downloadFile"/>
</p>
}
}
}
And here is the action method it's to hit, wanting to send in the userId, CarId, and carFileUrl:
[HttpGet]
public string GetFileDownloadUrl(string carFileUrl, int carId, int userId)
{
string downloadUrl = string.Empty;
downloadUrl =GetDownloadUrl(carId, carFileUrl, userId);
return downloadUrl;
}
Here is my route:
context.MapRoute("CarFileDownload", "Car/{carId}/{userId}/{carFileUrl}", new { controller = "Content", action = "GetFileDownloadUrl", carFileUrl= UrlParameter.Optional, carId = UrlParameter.Optional, userId = UrlParameter.Optional });
When I click a button, it's not posting back to my GetDownloadUrl actionmethod.
I don't know if I just don't have the route setup right in terms of if I need all those optional params set and then in my BeginForm do I need to specify all of them again or not since I have some of them as hidden fields being posted back? Not sure why it is not hitting my GetFileDownloadUrl action method in this case...
Have a hidden field for the car type, and use JQuery or similar to make the click event of the hyperlink set the car type hidden field.
Using the concept of 'progressive enhancement' you can make you page still function for those users who may not have javascript enabled - simply make the links actually do the get and make the button hidden. Then, using JQuery, override the default behaviour of the link (as described above) and make the button visible. That way you will get your desired behaviour, but your page will still function for those without javascript.
there's nothing preventing you to create many forms in one page.
if(Model.Cars != null && Model.Cars.Count > 0)
{
foreach (CarContent car in Model.Cars)
{
<div>
#using (Html.BeginForm("GetCarUrl", "Car", FormMethod.Get, new { model = car }))
{
<p>#car.Name</p>
#Html.Hidden("CarId", car.Id)
<input type="submit" value="Get Car Url" class="submit" />
}
</div>
}
}
note that each form model = car

Using two button in a form calling different actions

I have a form on my page:
#using(Html.BeginForm("DoReservation","Reservation"))
{
...some inputs
<button id="recalculate">Recalculate price</button>
<button id="submit">Submit</button>
}
When I click the "Recalculate price" button I want the following action to be invoked:
public ActionResult Recalculate(FormCollection form)
{
var price = RecalculatePrice(form);
... do some price recalculation based on the inputs
return PartialView("PriceRecalculation",price);
}
When I click the "Submit" button I want the "DoReservation" action to be invoked (I want the form to be submitted).
How can I achieve something like that?
What I can suggest is , adding a new property to your view model and call it ActionType.
public string ActionType { get; set; }
and then change your cshtml file like below
#using (Html.BeginForm())
{
<div id="mytargetid">
...some inputs*#
</div>
<button type="submit" name="actionType" value="Recalculate" >Recalculate price</button>
<button type="submit" name="actionType" value="DoReservation" >Submit</button>
}
in post action method based on ActionType value you can decide what to do !
I noticed that in your comments you mentioned you need to return partial and replace if with returning partial , no problem , you can use
#using (Ajax.BeginForm("DoProcess", new AjaxOptions { UpdateTargetId = "mytargetid", InsertionMode = InsertionMode.Replace }))
and in controller change your action to return partial view or java script code to redirect page
public ActionResult DoProcess(FormModel model)
{
if (model.ActionType == "Recalculate")
{
return PartialView("Test");
}
else if (model.ActionType == "DoReservation")
{
return JavaScript(string.Format("document.location.href='{0}';",Url.Action("OtherAction")));
}
return null;
}

Correct way to bind an mvc3 radiobutton to a model

I have a view that contains a radiobutton list for my terms and conditions of the site.
e.g.
Yes
#Html.RadioButtonFor(model => model.TermsAndConditions, "True")
No
#Html.RadioButtonFor(model => model.TermsAndConditions, "False",
new { Checked = "checked" })
</div>
#Html.ValidationStyledMessageFor(model => model.TermsAndConditions)
All is ok if the user completes the form without any errors however if I do serverside validation and the page is refreshed I lose the selection that the user made for the radiobutton and the selected radio goes back to the default false field.
How am I meant to be binding the radiobutton so if a user selects true this value is maintained even after serverside validation?
Any suggestions would be great!
For the short answer, you need to do three things:
Remove the new { Checked = "checked" } from the second radio button. This hard-coded checked value will override all of the magic.
When you return your ViewResult from the controller action, give it an instance of your model class where TermsAndConditions is false. This will provide the default false value you need in order to have the false radio button preselected for you.
Use true and false as the values for your radio buttons instead of "True" and "False". This is because your property is of type bool. Strictly speaking, you coincidentally chose the correct string representations for true and false, but the value parameter for the RadioButtonFor method is of type object. It's best to pass in the actual type you want to compare to rather than converting it to a string yourself. More on this below.
Here's what's going on in depth:
The framework wants to do all of this for you automatically, but you did those first two things incorrectly which makes you have to fight with the framework to get the behavior you want.
The RadioButtonFor method calls .ToString() on the value of the property you specified and compares it to the .ToString() of the value you passed in when creating the radio button. If they are equal, then it internally sets isChecked = true and ends up rendering checked="checked" in the HTML. This is how it decides which radio button to check. It simply compares the value of the radio button to the value of the property and checks the one that matches.
You can render radio buttons for pretty much any property this way and it will magically work. Strings, ints, and even enum types all work! Any object that has a ToString method that returns a string which uniquely represents the object's value will work. You just have to make sure you're settings the radio button's value to a value that your property might actually have. The easiest way to do this is just to pass in the value itself, not the string representation of the value. Let the framework convert it to a string for you.
(Since you happened to pass in the correct string representations of true and false, then those values will work as long as you fix your two actual mistakes, but it's still wise to pass in the actual values and not their strings.)
Your first real mistake was hard-coding Checked = "checked" for the "No" radio button. This will override what the framework is trying to do for you and results in this radio button always being checked.
Obviously you want the "No" radio button to be preselected, but you have to do it in a way that's compatible with everything above. You need to give the view an instance of your model class where TermsAndConditions is set to false, and let it "bind" that to the radio buttons. Normally, a controller action which responds to the initial GET request of a URL doesn't give the View an instance of the model class at all. Typically, you just return View();. However, since you want a default value selected, you must provide the view with a instance of your model that has TermsAndConditions set to false.
Here is some source code illustrating all of this:
Some sort of Account class that you probably already have. (Your View's model):
public class Account
{
public bool TermsAndConditions { get; set; }
//other properties here.
}
Some methods in your controller:
//This handles the initial GET request.
public ActionResult CreateAccount()
{
//this default instance will be used to pre-populate the form, making the "No" radio button checked.
var account = new Account
{
TermsAndConditions = false
};
return View( account );
}
//This handles the POST request.
[HttpPost]
public ActionResult CreateAccount( Account account )
{
if ( account.TermsAndConditions )
{
//TODO: Other validation, and create the account.
return RedirectToAction( "Welcome" );
}
else
{
ModelState.AddModelError( "TermsAndConditionsAgreement", "You must agree to the Terms and Conditions." );
return View( account );
}
}
//Something to redirect to.
public ActionResult Welcome()
{
return View();
}
The entire View:
#model Account
#{
ViewBag.Title = "Create Account";
}
#using ( Html.BeginForm() )
{
<div>
<span>Do you agree to the Terms and Conditions?</span>
<br />
#Html.RadioButtonFor( model => model.TermsAndConditions, true, new { id = "TermsAndConditions_true" } )
<label for="TermsAndConditions_true">Yes</label>
<br />
#Html.RadioButtonFor( model => model.TermsAndConditions, false, new { id = "TermsAndConditions_false" } )
<label for="TermsAndConditions_false">No</label>
<br />
#Html.ValidationMessage( "TermsAndConditionsAgreement" )
</div>
<div>
<input id="CreateAccount" type="submit" name="submit" value="Create Account" />
</div>
}
BONUS: You'll notice that I added a little extra feature to the radio buttons. Rather than just use plain text for the radio button labels, I used the HTML label element with the for attribute set to the IDs of the each radio button. This lets users click on the label to select the radio button instead of having to click on the radio button itself. This is standard HTML. For this to work I had to set manual IDs on the radio buttons, otherwise they would both get the same ID of just "TermsAndConditions", which wouldn't work.
There are a few things you need to do here in order to ensure the user's selection is maintained after server side validation.
a) Bind the "checked" property of each radio to your model in the view, for example:
Yes
#Html.RadioButtonFor(model => model.TermsAndConditions, "True", model.TermsAndConditions == true ? new { Checked = "checked" } : null)
No
#Html.RadioButtonFor(model => model.TermsAndConditions, "False", model.TermsAndConditions == false ? new { Checked = "checked" } : null)
b) To define the initial default value when the view is first displayed, initialise the model returned to the view in the GET request (in the controller action), for example:
public ActionResult SomeForm()
{
return View(new SomeModel { TermsAndConditions = false });
}
b) Ensure in your [HttpPost] controller action that you return the model when the validation fails, for example:
[HttpPost]
public ActionResult SomeForm(SomeModel model)
{
if (!ModelState.IsValid)
return View(model);
// Do other stuff here
}
This way when the view is rendered in the response after validation fails, it will have the actual model state that was passed in (thus maintaining the user's selection).
I can't really tell since you haven't shown your code, but I suspect that if you're failing on server side validation you're just returning the raw view. When it fails, you need to populate the view with the model that was submitted, same as if you were returning any other validation errors. Otherwise you'll get the default model values (which will always be false for the registration boolean).
Maybe you could post your server side code?
Here I am offering another more complex example.
public enum UserCommunicationOptions
{
IPreferEmailAndSMS = 1,
IPreferEmail = 2,
IPreferSMS = 3
}
Html
#model UserProfileView
// Some other code
<div class="form-group">
<label class="col-lg-2 control-label">Communication</label>
<div class="col-lg-10">
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmailAndSMS.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS)
}
<label class=" control-label" for="#Model.UserCommunicationOption">I Prefer Email And SMS</label>
</div>
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmail.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail)
}
<label class=" control-label" for="#Model.UserCommunicationOption">I Prefer Email</label>
</div>
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferSMS.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS)
}
<label class=" control-label" for="#Model.UserCommunicationOption">#DLMModelEntities.Properties.Resource.IPreferSMS</label>
</div>
</div>
</div>
Model
[Required(ErrorMessageResourceName = "Communications", ErrorMessageResourceType = typeof(Resource))]
[Display(Name = "Communications", ResourceType = typeof(DLMModelEntities.Properties.Resource))]
public UserCommunicationOptions UserCommunicationOption { get; set; }
GET
var client = AppModel.Clients.Single(x => x.Id == clientId);
if (Convert.ToBoolean(client.IsEmailMessage) && Convert.ToBoolean(client.IsSMSMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferEmailAndSMS;
}
else if (Convert.ToBoolean(client.IsEmailMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferEmail;
}
else if ( Convert.ToBoolean(client.IsSMSMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferSMS;
}
POST
[HttpPost]
public ActionResult MyProfile(UserProfileView model)
{
// Some code
var client = AppModel.Clients.Single(x => x.Id == clientId);
if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmail)
{
client.IsSMSMessage = false;
client.IsEmailMessage = true;
}
else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmailAndSMS)
{
client.IsSMSMessage = true;
client.IsEmailMessage = true;
}
else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferSMS)
{
client.IsSMSMessage = true;
client.IsEmailMessage = false;
}
AppModel.SaveChanges();
//Some code
}
Database
Webpage
I had a similar issue and solved the problem by setting a ViewData value in controller to keep track of what the user had selected.

Resources