I have two independent classes that model my tables. First when a new user is created, the user does not have a record in the certificate tables. So in the view for the certificates I have added a button to add certificates details for this new user.
This is my code for the user view: I omitted the paging/search and filter code to make it simple
public ActionResult Index()
var recipients = from s in db.User
select s;
return View(recipients.ToList());
This is the details view showing related data:
public ViewResult Details(int id)
{
var certificateDetails = db.Certificate.Where(p => p.ID == id);
return View(certificateDetails);
}
Adding a new user means also adding a new certificates details. I want when a user clicks details for the a particular user if those details aint around to be redirected to a create certificate view with both User.ID and CertificateID set. In fact CertificateID is AI but ID from User is foreign key.
I would have used Fluent API but am not good with it either so have to handle this seemingly small challenge in code.
If I understand your question correctly, you want it so that when you view Details(), if the certificate details don't exist, then redirect to a page to create them?
Just check whether or not the entity exists. If it doesn't, return a RedirectToAction() and pass whatever data you need in the route data collection.
public ViewResult Details(int id)
{
var certificateDetails = db.Certificate.FirstOrDefault(p => p.ID == id);
if (certificateDetails == null)
return RedirectToAction("Create", "Certificate", new { userId = id });
return View(certificateDetails);
}
You'll also need to create a Certificate controller with a Create() action.
Related
I'm quite new in MVC and can't get my head around a possible overposting threath. I have an "Event" model which contains an Id property. When a user, for example, wants to edit an existing "Event", I use this property for fetching the "Event" I need to update from a collection of "Events".
I tried to decorate the Id property with the [BindNever] or [Editable] attribute which results in an Id property of 0 as the property no longer binds after a post. This, of course, generates problems when I want to use this Id property for fetching an "Event" from the collection.
So I leave the property undecorated. But it feels unnatural as this property should not be editable by a user. Using a ViewModel does not solve my problem as an Id property would still be needed.
In all examples I find online, an Id property is always part of a binding model. Does this pose threats to possible overposting? I assume not as, when working with Entity Framework, for example, the Id property is not Editable as it is autoincremented. A user would still be able to change the Id in order to update another "Event" though but in the case of my application, this generates no issue as a user is able to edit any "event" he wants
Model:
public class Event
{
public int Id { get; set; }
public string Name { get; set; }
}
Controller:
[HttpGet]
public IActionResult EditEvent(int? id)
{
if (id == null)
{
return NotFound();
}
else
{
var eventToEdit = _events.GetEvent(id.Value);
return View(eventToEdit);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult EditEvent(Event postedEvent)
{
if (ModelState.IsValid)
{
if (postedEvent == null)
{
return NotFound();
}
else
{
var eventToUpdate = _events.GetEvent(postedEvent.Id);
eventToUpdate = _events.EditEvent(postedEvent, eventToUpdate);
return RedirectToAction(nameof(EventDetails), new { id = eventToUpdate.Id });
}
}
else
{
return View(postedEvent);
}
}
Does ID property in binding model pose a threat to overposting?
Yes, if you're using this id as a lookup for editing then you need to write a check that ensures that the user is allowed to perform the edit on the data.
public IActionResult EditEvent(Event postedEvent)
{
//Make sure the current user can edit the posted event
if(!CanUserEditEvent(postedEvent.Id, User.GetUserId()) return Forbid();
//User can edit this event so continue normally
if(ModelState.IsValid)
...
}
Furthermore, ensure that other "read-only" or unused properties of your model that are exposed on the view and action are not then used as a lookup or saved. A view model can help with this by allowing you to define a subset of your domain model properties.
I'm creating a small MVC application and am passing a User object from a controller to an ActionResult method in another controller. One of the attributes of the User object is a list of Property objects called Properties.
Here's the rub: when the User object is finally passed to the relevant View, it's list does not contain any properties.
Here's the setup:
User class:
public class User
{
public int Id {get;set;}
public List<Property> Properties {get;set;}
}
AccountController
public ActionResult LogOn(int userId, string cryptedHash)
{
//code to logOn (this works, promise)
User user = dbContext.getUser(userId);
//debugging shows the user contains the list of properties at this point
return RedirectToAction("UserHome", "Home", user);
}
HomeController
public ActionResult UserHome(User user)
{
ViewBag.Messaage = "Hello, " + user.Forename + "!";
return View(user); //debugging shows that user.Properties is now empty(!)
}
UserHome.cshtml View
#model emAPI.Model_Objects.User
#{
ViewBag.Title = "UserHome";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>UserHome</h2>
<div>
#Model.Forename, these are your properties:
<ul>
#foreach (var property in #Model.Properties)
{
<li>property.Name</li>
}
</ul>
</div>
The view loads without any problem - #Model.Forename is fine, but as far as HomeController is concerned user.Properties was empty when it received it, although I know it wasn't when AccountController sent it.
Any help or advice anyone has to offer would be gratefully received.
You cannot pass entire complex objects when redirecting. Only simple scalar arguments.
The standard way to achieve that is to authenticate the user by emitting a forms authentication cookie which will allow you to store the user id across all subsequent actions. Then if in a controller action you need user details such as forename or whatever you simply query your data store to retrieve the user from wherever it is stored using the id. Just take a look at the way the Account controller is implemented when you create a new ASP.NET MVC 3 application.
So:
public ActionResult LogOn(int userId, string cryptedHash)
{
//code to logOn (this works, promise)
User user = dbContext.getUser(userId);
//debugging shows the user contains the list of properties at this point
// if you have verified the credentials simply emit the forms
// authentication cookie and redirect:
FormsAuthentication.SetAuthCookie(userId.ToString(), false);
return RedirectToAction("UserHome", "Home");
}
and in the target action simply fetch the user id from the User.Identity.Name property:
[Authorize]
public ActionResult UserHome(User user)
{
string userId = User.Identity.Name;
User user = dbContext.getUser(int.Parse(userId));
ViewBag.Messaage = "Hello, " + user.Forename + "!";
return View(user);
}
Ah and please, don't use ViewBag. Use view models instead. If all that your view cares about is welcoming the user by displaying his forename simply build a view model containing the forename property and then pass this view model to the view. The view doesn't care about your User domain model and it shouldn't.
RedirectToAction method returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. You should not think about passing a complex object in that to the next action method.
In this case, may be you can keep your user object in the Session variable and access it in the remaining places.
public ActionResult LogOn(int userId, string cryptedHash)
{
User user = dbContext.getUser(userId);
if(user!=null)
{
Session["LoggedInUser"]=user;
return RedirectToAction("UserHome", "Home");
}
}
public ActionResult UserHome()
{
var loggedInUser= Session["LoggedInUser"] as User;
if(loggedInUser!=null)
{
ViewBag.Messaage = "Hello, " + user.Forename + "!";
return View(user);
}
return("NotLoggedIn");
}
So I have three roles, administrators, companies and employees in my mvc .net application that uses asp.net membership in a separate database. I moved the .net membership in a different database for now because everytime I modify the model, the .net membership tables are getting deleted.
Anyway, I am handling different roles using if/else in the action method. For example, in Index() action, I check if the user is in administrators role, then create model and linq query based on that. If user in companies role, different query and if user in employees role, different query. check code below. The model created after the if condition is passed to the View.
I feel like this is not the best way to handle roles. Is this the best way to handle roles? I am considering different areas as well, but I use same views for the different roles, i think it may not be productive.
Any suggestion/idea greatly appreciated.
[Authorize]
public class CompanyController : Controller
{
private MyDBContext db = new MyDBContext();
//
// GET: /Company/
public ViewResult Index()
{
var viewModel = new CompanyIndexViewModel();
if (Roles.IsUserInRole("administrators")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.ToList() };
}
else if (Roles.IsUserInRole("companies")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.Where(c => c.Username.ToLower().Equals(this.User.Identity.Name.ToLower())).ToList() };
}
else if (Roles.IsUserInRole("employees")) {
string userName = this.User.Identity.Name.ToLower();
var companies = db.Companies.Where(c => c.Appointments.Any(a =>
a.Employee.Username.ToLower() == userName)).ToList();
viewModel = new CompanyIndexViewModel { Companies = companies.ToList() };
}
return View(viewModel);
}
....
There is two things I would do:
Firstly, what StanK said and move it out of the controller action. However, I would move it out of the Controller all together. This sort of logic shouldn't really reside in the Controller to begin with (whether it be in an action, or a private method in the controller).
Think about it this way: What if your logic for who gets to see what companies changes.. you'll have to change it in all sorts of different places.
Secondly, I would create a constructor for the CompanyIndexViewModel that took in a list of Company instead of initializing it inline like that. Does the CompanyIndexViewModel contain anything else besides companies?
// your controller
public ViewResult Index()
{
var viewModel = CompanyIndexViewModel(CompanyService.GetCompaniesForCurrentUser());
return View(viewModel);
}
Ideally, you would also have your controller depend on an interface representing the "CompanyService", and have that injected into your controller.
Take a look at this blog which outlines using Ninject with MVC 3. It's ridiculously simple to setup for something that is so powerful for you later on.
If you take one thing away from what I've said above, it's probably best to start with moving your logic out of the controller.
I would move the code that builds the list of Companies to it's own method to tidy up the controller action , which would also make the logic that determines the list of Companies for the current user re-useable.
e.g.
private List<Company> GetCompaniesForCurrentUser()
{
var userName = this.User.Identity.Name.ToLower();
if (Roles.IsUserInRole("administrators"))
return db.Companies.ToList();
if (Roles.IsUserInRole("companies"))
return db.Companies.Where(c => c.Username.ToLower().Equals(userName)).ToList();
if (Roles.IsUserInRole("employees"))
return db.Companies.Where(c => c.Appointments.Any(a =>
a.Employee.Username.ToLower() == userName)).ToList();
throw new AuthorizationException("User " + userName + " is not authorised.");
}
public ViewResult Index()
{
var viewModel = new CompanyIndexViewModel { Companies = GetCompaniesForCurrentUser() };
return View(viewModel);
}
I hope i'm not bithering with my many questions...
i'm creating users in the ASPNETDB.MDF database with the asp.net configuration.
i have a client MVC, and i want when a client is created, edited, or deleted, it must dispaly the user_name and last_user_name of the user that performed the action. These two properties r in the client model. i want my controller to only deal with the ID(PK) of the users, and not their names. I only know how to make the controller work with the user names. How do i ensure that any functionality is done with the id but the display is the user_name? Here is my create method in my clientcontroller
[Authorize]
public ActionResult Create()
{
var model = new title
{
create_dt = DateTime.Now,
last_maint_dt = DateTime.Now,
row_version = 1,
status = "ACTIVE",
user_id = System.Web.HttpContext.Current.User.Identity.Name,
last_user_id = System.Web.HttpContext.Current.User.Identity.Name
};
return View(model);
}
If you are using the default ASP.NET Membership Provider you can find the user GUID for an account by accessing System.Web.Security.Membership. Then you can use the GetUser() method and pass it the username as an argument.
//Get Current User ID
var user = System.Web.Security.Membership.GetUser(User.Identity.Name);
if (user != null)
{
string id = user.ProviderUserKey.ToString();
}
And if you have the GUID and want the Username
var user = System.Web.Security.Membership.GetUser(id);
if (user != null)
{
string username = user.UserName;
}
I've been trying to add a new record.
public ActionResult Create()
{
var dc = new ServicesDataContext();
ViewData["CustomerID"] = TempData["CustomerID"];
var a = dc.services.Select(arg => arg.ServiceID).ToList();
ViewData["ServiceID"] = new SelectList(a);
var model = new Maping();
return View(model);
}
//
// POST: /Customerservice/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude ="CustomerServiceMappingID")] Maping serviceToCreate)
{
if (!ModelState.IsValid)
return View();
var dc = new ServicesDataContext();
dc.Mapings.InsertOnSubmit(serviceToCreate);
dc.SubmitChanges();
return RedirectToAction("Index", "Home");
}
Now the situation is that the tempdata has the correct value but by the time i submit changes the customerID turns out to be null. So, kindly help me in solving this.
AFAI understand you copy the customer id from TempData to ViewData. However, the contents of the ViewData will be not preserved after the request ends. In your view you should put the customer id into an input (e.g. a hidden field if it should be not displayed) to get it back in your post action.
If you use a strongly typed model, you should not use the ViewData at all, but rather you should set the customer id on the model instance. Then in the view you could use a Html.HiddenFor(m => m.CustomerId) to "preserve" this id.
I hope I did not misunderstand the question, unfortunately it is not really visible in your code snippets where you would have a customer id in the post action that is null.