Created ViewModel for EntityFramework Drop Down List - c#

I want to have a drop down that won't require to to query the database in the controller POST section in order to get the ID for the drop down selection so that it can be placed in table as a foreign key. I don't understand how it could be down without needing to make that query. I want entity framework to do the heavy lifting for it if that makes sense? Is this possible?
public class BillRate
{
public BillRate()
{
this.BillRateTickets = new List<Ticket>();
}
public long BillRateID { get; set; }
public decimal TicketBillRate { get; set; }
public virtual ICollection<Ticket> BillRateTickets { get; set; }
}
public class Ticket
{
public long TicketID { get; set; }
public virtual BillRate BillRate { get; set; }
}

It's not clear what exactly do you mean. If you do not query your database where where do you think the items to be displayed on the dropdown list will come from? They will definetely won't come from the view because in HTML when you submit a form containing a <select> element, only the selected value is ever sent to the server. The collection values are never sent, so ASP.NET MVC cannot invent those values for you.
If you want to avoid hitting your database you could store this list into the cache and inside your POST action try looking for the values in the cache first. But those values must be persisted somewhere on your server. So you could have a method which will look for the values in the cache first and if not found query the database:
private IEnumerable<Ticket> GetTickets()
{
// Try to get the tickets from the cache first:
var tickets = MemoryCache.Default["tickets"] as IEnumerable<Ticket>;
if (tickets == null)
{
// not found in cache, let's fetch them from the database:
tickets = db.Tickets.ToList();
// and now store them into the cache so that next time they will be available
MemoryCache.Default.Add("tickets", tickets, new CacheItemPolicy { Priority = CacheItemPriority.NotRemovable });
}
return tickets;
}
and then you could have the 2 controller actions:
public ActionResult Index()
{
var model = new BillRate();
model.BillRateTickets = GetTickets();
return View(model);
}
[HttpPost]
public ActionResult Index(BillRate model)
{
model.BillRateTickets = GetTickets();
return View(model);
}

Related

MVC 6 - How to require password to perform controller action?

I am creating an employee scheduling site in ASP.net MVC 6. I have an employee table, shift table and a shiftEmployee table to handle the many to many relationship.
It's configured so that each employee logs into the site using their employee ID number and a password. Then they can see each future shift they are scheduled to. They must acknowledge each assigned shift in a process known as "pulling their pin".
So far everything is working as expected. My goal and my question is this:
When an employee pulls their pin for each shift, I would like them to have to confirm this action by entering their password again, keeping in mind the user is already signed into the site. What is the easiest/correct/most secure way to accomplish this?
The Pull GET/POST methods are basically the same as a standard MVC edit action, simply renamed Pull.
// GET: PullPin/Pull/5
public IActionResult Pull(int? id)
{
if (id == null)
{
return HttpNotFound();
}
var shiftEmp = _context.ShiftEmployees.Single(m => m.ShiftEmployeeID == id);
if (shiftEmployee == null)
{
return HttpNotFound();
}
}
// POST: PullPin/Pull/5
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Pull(ShiftEmployee shiftEmployee)
{
var user = GetCurrentUserAsync();
pullPin.PinStatusID = 3; // Status ID #3 = Pulled
if (ModelState.IsValid)
{
_context.Update(shiftEmployee);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(shiftEmployee);
}
And here is my ShiftEmployee class
public class ShiftEmployee
{
public int ShiftEmployeeID { get; set; }
public int ShiftID { get; set; }
public int EmployeeID { get; set; }
public int PinStatusID { get; set; }
public virtual Shift Shift { get; set; }
[JsonIgnore]
public virtual Employee Employee { get; set; }
public virtual PinStatus PinStatus { get; set; }
}
In the standard MVC6 template, it uses ASP.NET Core Identity for the login functionality. Part of that package is the UserManager object (you also get a SignInManager among other things.)
The UserManager object has a method specifically for checking passwords called CheckPasswordAsync and is used like this:
_userManager.CheckPasswordAsync(user, password)

How to pass data from different controllers

I am trying to pass a value which is stored in one controller to another, code is below:
Charities Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Donate([Bind(Include = "ID,DisplayName,Date,Amount,Comment")] Charity charity)
{
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(charity.Comment))
{
var comment = charity.Comment.ToLower().Replace("hot", "###").Replace("cold", "###").Replace("Slow", "###").Replace("enjoy", "###").Replace("BAD", "###");
charity.Comment = comment; //Replaces textx from model variable - comment
charity.TaxBonus = 0.20 * charity.Amount;
}
if (string.IsNullOrEmpty(charity.DisplayName))
{
charity.DisplayName = "Annonymus"; //If user doesnt enter name then Annonymus
}
db.Donations.Add(charity);
db.SaveChanges();
TempData["Name"] = charity.DisplayName;
TempData["Amount"] = charity.Amount;
TempData["Comment"] = charity.Comment;
return RedirectToAction("../Payments/Payment", "Charities", new { id = charity.Amount });
}
return View(charity);
}
Charities Class
public class Charity
{
public int ID { get; set; }
[RegularExpression(#"^[a-zA-Z]+$", ErrorMessage = "Use letters only please")]
public string DisplayName { get; set; }
[DataType(DataType.Currency)]
[Range(2, Int32.MaxValue, ErrorMessage = "Atleast £2.00 or a whole number please")]
public int Amount { get; set; }
[DataType(DataType.Currency)]
public Double TaxBonus { get; set; }
public String Comment { get; set; }
public static object Information { get; internal set; }
}
Payment Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Payment([Bind(Include = "ID,CardName,CardNumber,ValidFrom,Expires,CardSecurityCode,EmailAddress,ConfrimEmailAddress,Address,City,Country,PostCode")] Payment payment)
{
if (ModelState.IsValid)
{
db.Payments.Add(payment);
db.SaveChanges();
TempData["Name"] = charity.DisplayName
TempData["Amount"];
TempData["Comment"];
TempData["Name"] = payment.CardName;
TempData["Email"] = payment.EmailAddress;
return RedirectToAction("Confirmation", "Payments", new { id = payment.ID });
}
return View(payment);
}
Payment Class
public class Payment
{
public int ID { get; set; }
}
public class CharityDBContext : DbContext //controls information in database
{
public DbSet<Charity> Donations { get; set; } //creates a donation database
}
public class PaymentDBContext : DbContext //controls information in database
{
public DbSet<Payment> Payments { get; set; } //creates a donation database
public System.Data.Entity.DbSet<CharitySite.Models.Charity> Charities { get; set; }
}
}
I am trying to get this from the Charities Controller
TempData["Name"] = charity.DisplayName;
To display in Payment controller
TempData["Name"] = charity.DisplayName;
Right now theres a squigly red line under "charity" in the payment controller with the message - doesnt exist in current context. I just wanted to know if it is possible to pass data from different controllers using temp data.
First of all, the line return RedirectToAction in your Donate method is going to send a 302 response to your browser which will issue a GET request to the url in the location header of the response, which in this case is Payment/Payment. But your Payment method is marked with HttpPost. Are you sure you want to send a second GET request to a method marked with HttpPost to save some part of the data(Payment) you want to save ?
I think you should save your charity and payment info in the same action method( Create a PaymentCharity view model and use that instead of using the Bind attribute and the entity classes created by EF to transfer data from your view to action method). Also, insteaof using TempData to pass data, What you should do is, get the unique id of the Payment record you saved, pass that in querystring to the second action method and in that using the unique payment id,read the payment record again and use that.
So in your Donate method,
public ActionResult Donate(PaymentCharirtVm model)
{
var charity = new Charity { DisplayName =model.Amount,Comment =model.Comment};
var payment = new Payment ();
//set the properties of payment here
db.Donations.Add(charity);
db.SaveChanges();
//now save Payment
db.Payment.Add(payment);
db.SaveChanges();
return RedirectToAction("Confirmation","Payment", new { id=payment.Id });
}
I have never tried to do it with tempdata. Have never needed to. I recomend you add the string with the charity name to the view model used by the payment controller.
Please correct code as below to retrieve value from TempData.
charity.DisplayName= TempData["Name"]
As already answered here, most answer are correct. It would be better to use routeValues in the RedirectToAction method to pass value from one action to another action in the same controller or different controller.
return RedirectToAction("actionName", "anotherControllerName", new { id = charity.DisplayName });
In the another controller, data can be retrieved by
string displayName = RouteData.Values["id"].ToString();
Here, in the routeValues, you can pass the whole Charity object as well and do some casting in the another controller to get the object properly in the that case when you need more than one properties to be sent:
return RedirectToAction("actionName", "anotherControllerName", new { id = charity });
And get it by:
Charity chatiry = (Charity)RouteData.Values["id"];
I hope that you will get some idea from this.

ViewModel becomes null when passed between actions

I'm having trouble passing a viewmodel into a view. I have two views: a Search view and a GeneralForm view. Search passes search results into GeneralForm's viewmodel.
Say the GeneralForm is a complex viewmodel that holds two other viewmodels:
public class GeneralFormViewModel
{
public GeneralInfoViewModel GeneralInfo { get; set; }
public NotesViewModel Notes { get; set; }
}
public class GeneralInfoViewModel
{
[Required(ErrorMessage = "Please enter the person's name.")]
[DisplayName("Name:")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter the person's ID.")]
[DisplayName("ID:")]
public int ID { get; set; }
}
public class NotesViewModel
{ // etc.
(I set up this way in order to use multiple #Html.BeginForms on my GeneralForm view. In this way, I hope to POST and validate small sections of the entire general form, one at a time, using KnockoutJS and AJAX.)
[HttpPost]
public ActionResult Search(SearchViewModel vm)
{
var query = // do some searching
var viewmodel = new GeneralFormViewModel()
{
GeneralInfo = new GeneralInformationViewModel
{
ID = query.id,
Name = query.name
}
};
return RedirectToAction("GeneralForm", viewmodel);
}
At this point, viewmodel.GeneralInfo is not null, and the viewmodel is passed to the GeneralForm controller.
[HttpGet]
public ActionResult GeneralForm(GeneralFormViewModel model)
{
return View(model);
}
Now model.GeneralInfo is null. What conventions of MVC am I breaking by doing this, and how can I get the GeneralForm view to render the data acquired via the Search controller to the GeneralForm view?
Problem is You can't send data with a RedirectAction.
you're doing a 301 redirection and that goes back to the client.
Store it in a TempData or Session ...
See the following post:
passing model and parameter with RedirectToAction

Rebuilding a nested ViewModel on !ModelState.IsValid

What are good strategies for rebuilding/enriching a nested or complex ViewModel?
A common way to rebuild a flat ViewModel is shown here
But building and rebuilding a nested ViewModel using that method is too complex.
Models
public class PersonInfo
{
public int Id { get; set; }
public string Name { get; set; }
public int Nationality { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address
{
public int AddressTypeID { get; set; }
public string Country { get; set; }
public string PostalCode { get; set; }
}
public class AddressType
{
public int Id { get; set; }
public string Description { get; set; }
}
view models
public class PersonEditModel
{
public int Id { get; set; }
public string Name { get; set; } //read-only
public int Nationality { get; set; }
public List<AddressEditModel> Addresses { get; set; }
public List<SelectListItem> NationalitySelectList { get; set; } //read-only
}
public class AddressEditModel
{
public int AddressTypeId { get; set; }
public string AddressDescription { get; set; } //read-only
public string Country { get; set; }
public string PostalCode { get; set; }
public List<SelectListItem> CountrySelectList { get; set; } //read-only
}
actions
public ActionResult Update(int id)
{
var addressTypes = service.GetAddressTypes();
var person = service.GetPerson(id);
var personEditModel= Map<PersonEditModel>.From(person);
foreach(var addressType in addressTypes)
{
var address = person.Addresses.SingleOrDefault(i => i.AddressTypeId == addressType.Id)
if(address == null)
{
personEditModel.Addresses.Add(new AddressEditModel
{
AddressTypeId = addressType.Id
});
}
else
{
personEditModel.Addresses.Add(Map<AddressEditModel>.From(address));
}
}
EnrichViewModel(personEditModel, person, addressTypes); //populate read-only data such as SelectList
return Index(personEditModel);
}
[HttpPost]
public ActionResult Update(PersonEditModel editModel)
{
if(!ModelState.IsValid)
{
var person = service.GetPerson(editModel.Id);
var addressTypes = service.GetAddressTypes();
EnrichViewModel(editModel, person, addressTypes);
return View(editModel);
}
service.Save(...);
return RedirectToAction("Index");
}
//populate read-only data such as SelectList
private void EnrichViewModel(PersonEditModel personEditModel, Person person, IEnumerable<AddressType> addressTypes)
{
personEditModel.Name = person.Name;
personEditModel.NationalitySelectList = GetNationalitySelectList();
foreach(var addressEditModel in personEditModel.Addresses)
{
addressEditModel.Description = addressTypes.Where(i => i.Id = addressEditModel.AddressTypeId).Select(i => i.Description).FirstOrDefault();
addressEditModel.CountrySelectListItems = GetCountrySelectList(addressEditModel.AddressTypeId);
}
}
My code for building and rebuilding the ViewModels (PersonEditModel and AddressEditModel) is too ugly. How do I restructure my code to clean this mess?
One easy way is to always build a new view model instead of merging/rebuilding since MVC will overwrite the fields with the values in ModelState anyway
[HttpPost]
public ActionResult Update(PersonEditModel editModel)
{
if(!ModelState.IsValid)
{
var newEditModel = BuildPersonEditModel(editModel.Id);
return View(newEditModel);
}
but I'm not sure that this is a good idea. Is it? Are there other solutions besides AJAX?
I'm going to tackle your specific pain points one-by-one and I'll try to present my own experience and likely solutions along the way. I'm afraid there is no best answer here. You just have to pick the lesser of the evils.
Rebuilding Dropdownlists
They are a bitch! There is no escaping rebuilding them when you re-render the page. While HTML Forms are good at remembering the selected index (and they will happily restore it for you), you have to rebuild them. If you don't want to rebuild them, switch to Ajax.
Rebuilding Rest of View Model (even nested)
HTML forms are good at rebuilding the whole model for you, as long as you stick to inputs and hidden fields and other form elements (selects, textarea, etc).
There is no avoiding posting back the data if you don't want to rebuild them, but in this case you need to ask yourself - which one is more efficient - posting back few extra bytes or making another query to fetch the missing pieces?
If you don't want to post back the readonly fields, but still want the model binder to work, you can exclude the properties via [Bind(Exclude="Name,SomeOtherProperty")] on the view model class. In this case, you probably need to set them again before sending them back to browser.
// excluding specific props. note that you can also "Include" instead of "Exclude".
[Bind(Exclude="Name,NationalitySelectList")]
public class PersonEditModel
{
...
If you exclude those properties, you don't have to resort to hidden fields and posting them back - as the model binder will simply ignore them and you still will get the values you need populated back.
Personally, I use Edit Models which contain just post-able data instead of Bind magic. Apart from avoiding magic string like you need with Bind, they give me the benefits of strong typing and a clearer intent. I use my own mapper classes to do the mapping but you can use something like Automapper to manage the mapping for you as well.
Another idea may be to cache the initial ViewModel in Session till a successful POST is made. That way, you do not have to rebuild it from grounds up. You just merge the initial one with the submitted one in case of validation errors.
I fight these same battles every time I work with Forms and finally, I've started to just suck it up and go fully AJAX for anything that's not a simple name-value collection type form. Besides being headache free, it also leads to better UX.
P.S. The link you posted is essentially doing the same thing that you're doing - just that its using a mapper framework to map properties between domain and view model.

Dynamically binding and creating items with complex database structure to Create and Edit views using viewModel in MVC 3 with EF4

I had bad times trying to figure out how to deal with this. Any help wold be appreciated. Even a suggestion of a better structure that fits to my needs: Construct category items in a category having an specification of how they can be in a category item property list.
This is used to, among other things, dinamicaly generate forms for creating and editing items.
Long story short: I need to know if I'm doing it right or a better (maybe automated) way of deal with it without breaking the whole app.
.
.
I'm working with MySQL 5 in VWD Express 2010 in a Win7 64bit machine with all MySQL drivers intalled (ODBC and .NET specific provider, the last one is not compatible with ASP.Net 4). Other problem rised here, but can be target for a separate question: I'm writing all of my models, 'cause MySql isn't compatible with the Linq to SQL (I can imagine why, but not sure).
.
.
Returning to the real topic:
My models are:
Category - Them main entity, with a name property, a collection of CategoryItemProperty entities and a collection of Item entities;
CategoryItemProperty - An entity with a name and some other properties that dictate how the Items in this category may be (field size, mask, input restriction, etc);
Item - The entity whose properties are based on the category properties;
ItemProperty - The properties of the items (field size, mask, input restriction, etc)
The code is something around this:
public class Category
{
public int CategoryId { get; set }
public string Description { get; set }
//...
public virtual List<CategoryItemProperty> ItemProperties { get; set; }
}
public class CategoryItemProperty
{
public int CategoryItemPropertyId { get; set; }
public string Label { get; set; }
public string Name { get; set; }
public int Size { get; set; }
public int MaxLenght { get; set; }
//...
public virtual Category Category { get; set; }
}
public class Item
{
public int ItemId { get; set; }
public string Description { get; set; }
public int CategoryId { get; set; }
//...
public virtual Category Category { get; set }
public virtual List<ItemProperty> Properties { get; set; }
}
public class ItemProperty
{
public int ItemPropertyId { get; set; }
public int ItemId { get; set; }
public int CategoryItemPropertyId { get; set; }
public string Value { get; set; }
//...
public virtual Item Item { get; set; }
public virtual CategoryItemProperty CategoryItemProperty { get; set; }
}
.
.
The big problem here, with this approach, is to generate the form and deal with the data on the controller side to be saved to the database.
.
.
A more detailed example wold be: Generate a simple contact form:
We create a Category with some field specification:
var category = new Category() { Description = "Simple Contact Form" };
MyEntitySet.Categories.Add(category);
MyEntitySet.SaveChanges();
//...
var name = new CategoryItemProperty() { Label = "Name", Size = 50, MaxLength = 50 };
var message = new CategoryItemProperty() { Label = "Message", Size = 50, MaxLength = 255 };
category.ItemProperties.Add(name);
category.ItemProperties.Add(message);
MyEntitySet.Entry(category).State = EntityState.Modified;
MyEntitySet.SaveChanges();
.
.
What I have came up until now is to create a ViewModel to pass the category info and its item property collection to the Create and Edit views and doing a loop through the ItemProperties to generate the fields and working in the ItemController, receiving the FormCollection and generating the Item and its ItemPropertys objects and saving them to the database. But this process is terrible and painfull:
.
Items/Create View:
#model MyApp.Models.CategoryItemModelView
#Html.EditorFor(m => m.Description);
...
#foreach(var property in Model.ItemProperties)
{
<label>#property.Label</label>
<input type="text" name="#property.Name" size="#item.Size" maxlength="#item.MaxLength" />
}
In the Controller:
// GET: /Items/Create/5
public ActionResult Create(int id)
{
var categoryItemModelView = new CategoryItemModelView();
categoryItemModelView.Populate(id); // this method maps the category POCO to the ViewModel
return View(categoryItemModelView);
}
// POST: /Items/Create/5
[HttpPost]
public ActionResult Create(int id, FormCollection formData)
{
if (ModelState.IsValid)
{
var category = MyEntitySet.Categories.Find(id);
var item = new Item
{
CategoryId = id,
Description = formData["Description"],
// ...
Category = category
};
MyEntitySet.Items.Add(item);
foreach(var property in category.ItemProperties)
{
var itemProperty = new ItemProperty
{
ItemId = item.ItemId,
CategoryItemPropertyId = property.Id,
Value = form[property.Name],
// ...
Item = item,
CategoryItemProperty = property
};
MyEntitySet.ItemProperties.Add(itemProperty);
}
MyEntitySet.SaveChanges();
return RedirectToAction("Index");
}
// Here I don't know how to return the typed data to the user (the form returns empty)
var categoryItemModelView = new CategoryItemModelView(id);
categoryItemModelView.Populate(id); // this method maps the category POCO to the ViewModel
return View(categoryItemModelView);
}
.
.
My problem rises in building the Create and Edit actions and its respective views (see above how I'm doing it right now). How to handle this case, when I have to use the Category.ItemProperties to generate the fields and store the information in an Item object and the field values in its ItemProperty object?
.
.
Please note: All this code is for example purposes only. My code is similar, but its handled by a specific controller and specific views to CRUD Categories and CategoryItemProperties and I have no problem with this.
.
.
Sorry for this long text. I've tryed to be as clearest as I can. If you need any more info, drop a comment, please.
Okay rcdmk! first of all my English is terrible and i'm here to just share with you my few experience.
I have build such a complex software in the past with MVVM(WPF) + EF4 + T4 to generate POCO's and i deal with Microsoft Blend to handle with actions, bindings and so on between the client and the viewmodels!
that's work great! i hope i helped you!
ps:
I dont know if Blend supports ASP.Net but creating POCO(Viewmodel) with lazy loading could help u somewhere!
As i understand , Category and corresponding CategoryPropertyItems is describing how Item will be created . Simply Category is drawing an abstract form and item and item properties are concretes (because item property has Value property). So in Item/Create Action (GET) you can build item and it's properties using Category and CategoryPropertyItems.
public Item Build(Category category)
{
Item item = new Item();
item.Category = category;
item.ItemId = ...
foreach(var categoryItemProperty in category.ItemProperties)
{
ItemProperty itemProperty = new ItemProperty();
itemProperty.Item = item;
itemProperty.CategoryItemProperty = categoryItemProperty;
itemProperty.ItemPropertyId = ...
}
return item;
}
In result of Index/Create Action you can use either just this Item object or you can put item into ViewModel .
In View you can bind direcly to model (Item) properties .
This links can help you .
Editing and binding nested lists with ASP.NET MVC 2
Model Binding To A List

Categories