pardon me if this is a noob question but I have already searched on SO, google, and spend over an hour with an ASP.NET MVC 4 PRO book reading on Rendering Stronly Typed Helpers etc. I am just beginning learning ASP.NET 4 MVC so please go easy on me.
I have a model class called USER and a property called Name with an overwritten getter and setter (its me calling it this way, im not sure if this is the proper naming in the case)
//using...
namespace MvcMyApplication1.Models
{
public class User
{
[ScaffoldColumn(false)]
public int Id { get; set; }
public string Name
{
get
{
return WindowsIdentity.GetCurrent().Name.Split('\\')[1];
}
set
{
Name = WindowsIdentity.GetCurrent().Name.Split('\\')[1];
}
}
[Required(ErrorMessage="Password is required")]
public string Password { get; set; }
}
}
So, In my View I am trying to display the result of the get function, but I am lost and not sure how to do this.
#using(Html.BeginForm())
{
#Html.ValidationSummary(excludePropertyErrors: true);
// changed from LabelFor to DisplayFor
#Html.DisplayFor(m => m.Name)<br />
#Html.PasswordFor(m => m.Password)<br />
<input type="submit" value="Log in" />
}
I have tried to add attributes but then I am not sure how to assign the Name= to the get function
[DisplayName(Name="I want to call the get function here")]
public string Name { get; set; }
In my controller I have this code:
[HttpGet]
public ActionResult Index()
{
User newUser = new User();
return View();
}
[HttpPost]
public ActionResult Index(User m)
{
if (ModelState.IsValid)
{
return View("Report", m);
}
{
return View(m);
}
}
And this goes to the Report View
which normally displays the Windows login
#using (Html.BeginForm())
{
#Html.DisplayForModel()
}
EDIT: After swapping the LabelFor with DisplayFor the windows login is rendered but only after clicking the log in button. It does not render the first time I open the page
You're not passing in the model to the initial view:
return View();
should be
return View(newUser);
You should be using DisplayFor not LabelFor if you want to retrieve the property value.
#Html.DisplayFor(m => m.Name)
To get current authenticated user in the view, you can use #User.Identity.Name.
In controller, use User.Identity.Name
To display "I want to call the get function here" in DisplayName attribute in the view, use #Html.DisplayNameFor(m => model.Name)
Use WindowsIdentity in your class is not a good idea. For MVC, better use User.Identity.Name (use this in controller), the reason is this way it is get the user name under current HttpRequest, use WindowsIdentity might be fine, but careful. Depending on how you use it, it might return error or return the service account which ran the application pool.
Normally, getter and setter are written like this by calling a private property:
public class User{
private string _name;
public string Name
{
get{
return _name;
}
set{
_name = WindowsIdentity.GetCurrent().Name.Split('\\')[1];
}
}
It looks like your MVC app is using Windows Authentication, which is an intranet app, so your user will be automatically authenticated, which means logged on already.
Related
I have an application where I need to display a username and image for the current user. I could pull the data into a viewmodel and display in the view, however this would mean that i would need to do this in every controller that uses the view. Is there a way where I can global set values and call in some partial views without having to repeatedly duplicate it in each controller?
Sounds to me like you need to call a child action from your _Layout.cshtml. Here's how it could look.
First from what you have said I am assuming your viewModel will be this
public class UserDisplayViewModel
{
public string UserName { get; set; }
public string ImageUrl { get; set; }
}
You will need a controller that is responsible for getting the username and image data.
It would look like this
public class UserController : Controller
{
[ChildActionOnly]
public ActionResult _userDisplay()
{
var viewModel = GetUserNameAndImage();
return View(viewModel);
}
private UserDisplayViewModel GetUserNameAndImage()
{
//code for getting the username and image data
}
}
You would call the child action from your _layout.cshtml file like this
#Html.Action("_userDisplay", "User")
It goes wherever you want the username and image to appear in your HTML.
You will also need a partial view called _userDisplay.cshtml which will contain the markup for how you want to display your username and image. Here's a very basic example
#model UserDisplayViewModel
#{
Layout = null;
}
<p>
Username: #Model.UserName
</p>
<p>
<img src="#Model.ImageUrl"/>
</p>
This question already has an answer here:
ASP.Net MVC Postback a label value to your controller
(1 answer)
Closed 7 years ago.
Giving this simple model:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
this.Name = name;
this.Age = age;
}
public Person()
{ }
internal void Save()
{
}
}
With this cshtml:
<fieldset>
<legend>Person</legend>
Age of #Html.DisplayFor(m=> m.Name) :
#Html.TextBoxFor(m=> m.Age)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
And this Controller to show and save:
public class HomeController : Controller
{
public ActionResult Edit()
{
return View(new Person(name: "Test", age: 1));
}
[HttpPost]
public ActionResult Edit(Person p)
{
p.Save();
ViewBag.Message = "Did it";
return View(p);
}
}
With this example the user is presented with the name of the user and an input for its age, pretty much like this:
In this case since I'm using DisplayFor<T> to show its Name property is not post back to my Edit inside the Person class.
I can circumvent this by using a HiddenFor<T> for Name but looks like it's not the right way to do this (because you have the info twice in the source).
So, what is the correct/recommended/common sense way to show a property and have it post back to Controller?
There's nothing wrong with using HiddenFor -- in fact, that's probably how you should do it. It would be weird if you were also using EditorFor or something like that, but since this is the only place that you indicate you'd like to post the data to that action, you're totally clear to do it.
Evening all, I'm chasing my tail trying to get the structure of a simple ASP.NET MVC correct. From the start I should say I'm completely new to ASP and MVC, I've used a bit C# before.
For examples sake let's say I'm trying to check two pieces of information e.g. a username and password that a user enters, against a database that stores all the user info. If they put in the correct credentials a summary of the user info is displayed, if not they are taken back (well visually never leave) the login page with a please try again message.
So I have my Home (Login page) view, a HomeController and a HomeIndexViewModel. Similarly I have an Account view, a AccountController and an AccountUserViewModel (there is also AccountIndexViewModel but that isn't really used).
The Home view 'takes in' (through it's controller) a HomeIndexVM as it's model:
#model ViewModels.HomeIndexViewModel
#using (Html.BeginForm("User", "Account", FormMethod.Post))
{
if (#Model.PreviousAttempts)
{
<p><b>Username or password were not recognised please try again.</b></p>
}
<p>Username: #Html.TextBoxFor(x => x.Username)</p>
<p>Password: #Html.PasswordFor(x => x.Password)</p>
<input id="btnLogin" type="submit" value="Login" />
}
HomeController:
public class HomeController : Controller
{
public ActionResult Index(bool invalidLogin = false)
{
var vm = new HomeIndexViewModel() { Username = string.Empty, Password = string.Empty, PreviousAttempt = invalidLogin };
return View(vm);
}
}
And finally the HomeIndexViewModel.cs
public class HomeIndexViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public bool PreviousAttempt { get; set; }
}
I think that is okay so far. Now on clicking the login button, it will post to Account, User.
public ActionResult User(UserLogin userLogin)
{
if (!ModelState.IsValid)
return RedirectToAction("Index", "Home", new { invalidLogin = true });
// Match username and password against database
User user = userLogin.IsValid(userLogin.Username, userLogin.Password);
if (user != null)
{
return this.View(user);
}
return RedirectToAction("Index", "Home", new { invalidLogin = true });
}
There is a couple of things here, you can see the redirects back to the login page with the true flag to show the failed login message.
But more to my point, clearly it won't work as it takes a UserLogin as a parameter. This is a model object that contains:
public class UserLogin
{
private NewDBSolution_v1Entities accountsDB = new NewDBSolution_v1Entities();
[Required, MinLength(2)]
public string Username { get; set; }
[DataType(DataType.Password), Required]
public string Password { get; set; }
public bool PreviousAttempts { get; set; }
public User IsValid(string username, string password) // Ideally use the local username and password properties rather than pass in as they are the same.
{
// Match username and password against database and return full user info if match found, otherwise return null
}
}
So what am I asking... well is it best practice for, in this case, the Account's User Action to take the HomeIndexViewModel, even although it's Home as opposed to Account related? Or should I pass in a model object as I had originally done, and use it to the validation (what I don't like about this is the validation is done in object that is passed from a different view if that makes sense?)
How do you best bundle information up from a view to pass to an Action? I realize VMs and Model objects the compiler doesn't care they are just classes but I'd like to get my separation of concerns correct.
Basically here all I need is the username and password from the Home view, should that be bundled in to a VM or a M?
It just seems to me there is potential for an awful lot of classes that are just slightly different, so why not make one and use it. I suppose that is where inheritance comes in, but do you gain much there if ever subclass just adds one different property?
Anyway I keep going in circles about the best way to structure. I did read somewhere that VMs should basically be a mask/adapter over the model such that the view only sees just what it needs. But these VMs don't have a model associated with them.
I'm rambling now, if anyone can make head or tail of this and give me a few pointers I'd be very grateful, thanks.
I think you might be going round in circles because you've created the login view in the HomeController. Login code is Account-related, so why not put this into the AccountController instead?
I tend to favour posting to the same action as you get from, if you follow the Post-Redirect-Get pattern, as you're trying to do here. I would move the login action into the AccountController. I would move the code that does the database check into a separate class, rather than keeping it in the model. Perhaps a membership provider or something like that. I would pass that provider into the Controller - this allows the Controller and the Model object to stop worrying about how to decide whether a user is valid or not. They can just ask the provider to tell them.
It's generally good practice to keep your entities (whether UI or domain) simple. They certainly should have database connection objects inside them.
Your Controller would look something like this:
public class AccountController : Controller
{
private readonly IMembershipProvider membershipProvider;
public AccountController(IMembershipProvider membershipProvider)
{
this.membershipProvider= membershipProvider;
}
public ActionResult Login()
{
var viewModel = new LoginViewModel();
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
var user = membershipProvider.Find(viewModel.Username, viewModel.Password);
if (user != null)
{
membershipProvider.SignIn(user, true);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("Login", "Your credentials were not recognised. Please try again.");
return View(viewModel);
}
}
Because the Login action has the same name for both Get and Post, your view becomes simpler. You'd have a Login.cshtml view in the Views/Account folder like this:
#model ViewModels.LoginViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken() // good practice to add this, ties in with the ValidateAntiForgeryToken attribute on the Post action
#Html.ValidationSummary() // displays model errors if there are any
<p>Username: #Html.TextBoxFor(x => x.Username)</p>
<p>Password: #Html.PasswordFor(x => x.Password)</p>
<input id="btnLogin" type="submit" value="Login" />
}
To answer this specific question:
How do you best bundle information up from a view to pass to an Action?
You let the default model binder take care of that if you can. It's very powerful and can handle most situations in regards to data being passed back from a form. All the model binder does is match name-value pairs from the form collection that is passed back when the form is posted to properties on your class. Provided your class has the string properties Username and Password the model binder will populate them. The actual class that you provide is irrelevant as far as the model binder is concerned. This would work equally well (but anyone else working on the project wouldn't thank you for it!):
public class Dave
{
[Required, MinLength(2)]
public string Username { get; set; }
[DataType(DataType.Password), Required]
public string Password { get; set; }
}
And then in the Post action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(Dave dave)
{
...
}
Does that help?
I am fairly new to MVC and I have run into a problem persisting values on the page POST. Below is a simplified version of what I am trying to do. Basically I have a form where I display the users name and other details. On the POST ActionResult if the business rules fail the page should return to the current view else will proceed on but on my POST the name and address I set in the GET do not appear in the POST for the view model resulting in null values being sent back in the return View(model). Are there ways I can persists the values without having go back to the database to retrieve them?
Model
public class HomeModel
{
public string Forename { get; set; }
public string Surname { get; set; }
public string AddressLine1 { get; set; }
public bool Continue { get; set; }
}
Controller
[HttpGet]
public ActionResult Index()
{
var model = new HomeModel();
model.Forename = "Joe";
model.Surname = "Boe";
model.AddressLine1 = "Unknown";
return View(model);
}
[HttpPost]
public ActionResult Index(HomeModel homeModel)
{
if (homeModel.Continue)
return RedirectToAction("Index", "Form2");
else
return View(homeModel);
}
View
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.Forename, Model.Forename)<br />
#Html.LabelFor(m => m.Surname, Model.Surname)<br />
#Html.LabelFor(m => m.AddressLine1, Model.AddressLine1)<br /><br />
#Html.CheckBoxFor(m => m.Continue)<br /><br />
<input type="submit" value="Click" />
}
LabelFor doesn't create an HTML input type, so it's not included in the postback. You can make them TextBoxFors or, create HiddenFors alongside your labels so they persist back to the server
You can use a hidden field:
#Html.HiddenFor(model => model.ID)
This will then post the value back to the server. This will increase your data amounts for the post, and for large forms is inadvisable.
That said, you are probably better off rebuilding the data for the page load or doing server side caching, as otherwise you run the risk of injections of bad data. That may not matter to you if it is read only though.
I've looked, tried several different solutions and haven't found anything that works (at least, not something with an example close enough to what I want for me to follow). I'm sure I'm missing something that would be a simple thing to a more experienced coder. Help?
I have a Model called Residents. It includes ResidentID, PFName, PLName. I have a controller for Residents. I have CRUD views for Residents. All working just fine.
I have a Model called Logs. It includes LogID, ResidentID, Comments. I have a controller for Logs. I have CRUD views for Logs. All working just fine.
I can display all the log entries for a Resident. Works fine. After a Log entry has been created, I can display the PFName using the method
#Html.DisplayFor(model => model.Resident.PFName)
Next, I want to Create a new log entry for a selected Resident.
That's where I'm having the problem. I would like the "Create" view (for the Log) to display the ResidentFName and ResidentLName of the selected resident, not the ResidentID.
A this point, from the Details view for a Resident, I have a CreateLog link.
#Html.ActionLink("New Log Entry", "../Log/Create", new { #ResidentID = Model.ResidentID})
This (likely not the best way) gives me a URL with the value of the selected ID
http://localhost:999/Log/Create?ResidentID=1
The value for the ResidentID is correct; it changes depending on which Resident is selected.
This value is correctly entered
#Html.TextBoxFor(model => model.ResidentID)
on the new CreateLog page using the Log Controller Create action.
public ActionResult Create(int ResidentID)
I plan to hide the ResidentID TextBox so the user doesn't see it. It seems I have to make it available in the form to be able create a new log entry.
The CreateLog form currently works as I have it now. I can create a log entry and verify that entry has been correctly recorded for the Resident.
But, I would like the form to display the PFName and PLName for the Resident so the user has visible feedback for which Resident was selected.
I believe that the related data (PFName and PLName) I want has to be passed to the CreateLog form .... somehow. I can't get it from the form.
Since there's only the unsaved entry for ResidentID, I can't use the value from the CreateLog form it to display related data. As mentioned, for the Lists, there is no such problem. It's only for CreateLog.
I've tried adding the data to the URL. Not working. I've tried setting the strings in the Controller (and the URL). Not working. I've looked at setting a cookie, but haven't ever done that so not sure what to set or where to put it or how to get the values from it. I've looked at setting a variable in the controller ... (have that working to display drop down lists, but a list to select from is not what I need -- I want the matching values from the related table).
Log.LogID(PK, Identity)
Log.ResidentID(FK)
Resident.PFName
Resident.PLName
I can directly create a view with these tables/fields in my SQLDB and update it.
Assuming a view model which looks something like this:
public class CreateLogViewModel
{
public int ResidentID { get; set; }
public string PFName { get; set; }
public string PLName { get; set; }
public string SomeLogCreationProperty { get; set; }
// other properties
}
Your controller could look something like this:
public ActionResult Create(int ResidentID)
{
var model = db.Residents.Where(r => r.ResidentID == ResidentID)
.Select(r => new CreateLogViewModel
{
ResidentID = r.ResidentID,
PFName = r.PFName,
PLName = r.PLName
// other properties
});
return View(model);
}
Then the view:
#model CreateLogViewModel
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.ResidentID)
#Html.HiddenFor(m => m.PFName)
#Html.HiddenFor(m => m.PLName)
#Html.EditorFor(m => m.SomeLogCreationProperty)
// other properties
<input type="submit" />
}
This would then POST back to:
[HttpPost]
public ActionResult Create(CreateLogViewModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
// Redisplay the form with errors
return View(model);
}
Expanding on John H and StuartLC answers, you need to use ViewModels and the following workflow:
Database->(load)->Model->Controller->(convert)->ViewModel->View
and
View->ViewModel->Controller->(convert)->Model->(save)->Database
So lets says you have the following models:
namespace Models
{
public class Residents
{
public int ResidentID { get; set; }
public string PFName { get; set; }
public string PLName { get; set; }
//...
}
public class Logs
{
public int LogID { get; set; }
public int ResidentID { get; set; }
public string Comments { get; set; }
//...
}
}
You need a ViewModel that combines the data you need for display and input in your Log\CreateView:
namespace ViewModels
{
public class ResidentLog
{
public int ResidentID { get; set; }
public string PFName { get; set; }
public string PLName { get; set; }
public string Comments { get; set; }
//...
}
}
Then inside the controller:
public class LogController : Controller
{
[HttpGet]
public ActionResult Create(int ResidentID)
{
// Run in debug and make sure the residentID is the right one
// and the resident exists in the database
var resident = database.Residents.Find(residentID);
var model = new ViewModels.ResidentLog
{
ResidentID = resident.ResidentID,
PFName = resident.PFName,
PLName = resident.PLName,
Comments = string.Empty,
// ...
};
// Run in debug and make sure model is not null and of type ResidentLog
// and has the PFName and PLName
return View(model);
}
[HttpPost]
public ActionResult Create(ViewModels.ResidentLog model)
{
if (!ModelState.IsValid)
return View(model);
var log = new Models.Logs
{
// Assumes LogID gets assigned by database?
ResidentID = model.ResidentID,
Comments = model.Comments,
};
// Run in debug and make sure log has all required fields to save
database.Logs.Add(log);
database.SaveChanges();
return RedirectToAction("Index"); // Or anywhere you want to redirect
}
}
Then your Log\CreateView:
#model ViewModels.ResidentLog
<!-- Display the values needed -->
<div>#Model.ResidentID - #Model.PFName - #Model.PLName</div>
#using (var form = Html.BeginForm(...))
{
<!-- This saves the values for the post, but in fact only ResidentID is actually used in the controller -->
#Html.HiddenFor(m => m.ResidentID)
#Html.HiddenFor(m => m.PFName)
#Html.HiddenFor(m => m.PLName)
#Html.EditorFor(m => m.Comments)
<input type="submit" />
}
You need to provide the additional information to the view.
This can be done in at least 2 ways
Use the ViewBag dynamic as a quick and dirty cheap and cheerful container to pass everything the view needs from the controller.
(preferred) Use a custom ViewModel with a tailor made class which holds everything the view needs. This is generally preferred as it is statically typed.
(I'm assuming that resident is already persisted in the database by the time the Log controller is called - you might need to fetch it elsewhere)
So, in your log controller, here's an example of using ViewBag:
[HttpGet]
public ActionResult Create(int residentID)
{
ViewBag.Resident = Db.Residents.Find(residentId);
return View();
}
You can then show the resident properties on the view by utilizing the ViewBag.
Edit
Yes, by persisted I meant in the Db - apologies about using unclear jargon.
Here's another example of ViewBag approach (the idea is to create a new Comment for another object):
Doing this the cheap + cheesy ViewModel way - in the HTTPGet Controller Create method:
public ActionResult Create(string objectType, int objectId)
{
// This is equivalent to youn fetching your resident and storing in ViewBag
ViewModel.Object = FetchSomeObject(objectType, objectId);
return View();
}
And in the View I use this (The ViewBag is accessible to Controller and View):
<title>#string.Format("Add new Comment for {0} {1}", ViewBag.Object.ObjectType, ViewBag.Object.Name);</title>
As you say, you will also need to do add a hidden for the ResidentId in your create log form
As per #JohnH's answer (+1), the BETTER way to do this (than using the magic ViewBag dynamic) is to create a custom ViewModel specifically for this screen. The ViewModel can either be reused both ways (GET: Controller => View and POST : Browser => Controller, or you even have separate ViewModels for the Get and Post legs.
With much thanks to all, I have it working. The final piece was telling the controller to return the model (nl). Here's the full spec for what's working:
I have created a ViewModel that includes
public class NewLog
{
public int ResidentID { get; set; }
public string PFName { get; set; }
public string PLName { get; set; }
public string Comment { get; set; }
// other properties
}
In the LogController,
public ActionResult Create(int ResidentID)
{
var resident = db.Residents.Find(ResidentID);
var nl = new NewLog
{
ResidentID = ResidentID,
PFName = resident.PFName,
PLName = resident.PLName,
Comment = string.Empty,
};
return View(nl);
}
In the Create.cshtml page,
#model My.Models.NewLog
The required ResidentID to be recorded with the new Log Entry
#Html.TextBoxFor(model => model.ResidentID, new {#Type = "Hidden"})
And the related, user-friendly display boxes for the person's name
#Html.DisplayFor(model => model.PFName)
#Html.DisplayFor(model => model.PLName)
And in the URL which is used to access the create page,
#Html.ActionLink("New Log Entry", "../Log/Create", new { #ResidentID = item.ResidentID, item.PFName, item.PLName})