I'm creating my first ASP.NET MVC 3 website for my company's intranet. It's a pretty cool, I play audio recorded by our phone system and saved in our db. That's working good, but I'm having a hard time figuring out how to do something that should be simple. Please forgive any syntax errors I most likely have, this is a rough draft.
I have a table in the Index View /Apps that list all the AppName's, and next to each AppName I want to display a link to another view, with the text of the link being a Count() of all CallDetails associated with that App.
I have two classes:
public class Apps
{
public int AppId { get; set; }
public string AppName { get; set; }
}
public class CallDetail
{
public int Id { get; set; }
public int AppID { get; set; }
public byte[] FirstName { get; set; }
public byte[] LastName { get; set; }
....etc
}
a context for each:
public class AppsContext : DbContext
{
public DbSet<Apps> Apps { get; set; }
}
public class CallContext : DbContext
{
public DbSet<CallDetail> CallDetails { get; set; }
}
a controller method for each:
// AppsController
private AppsContext db = new AppsContext();
public ViewResult Index()
{
return View(db.Apps.ToList());
}
// CallController method (from my current attempt)
public ActionResult CallCheck(int id)
{
bool? enabled = null;
var appcalls = from s in db.CallDetails
where s.AppID == id
&& s.Enabled.Equals(enabled)
select s;
string callnum = appcalls.Count().ToString();
return View(callnum);
}
It displays the AppName just fine in this portion of the View below, and I can create a link to a View for each associated CallDetail just fine. But I don't know how to display info I'd get from the CallDetail Controller since the View's Model is Apps and its Controller, AppsController.
#model IEnumerable<myMessagePlayer.Models.Apps>
...
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.AppName)
</td>
<td class="appLink">
...
</td>
</tr>
}
I've tried many different methods, some that I might have gotten to work, but they seemed semantically un-MVC. So I figured I'd just ask a general "whats the standard practice?" type of question.
The path you are currently going down would end up hitting the database for each app you have in your database. There is a way to display all the information with only one hit to the database.
Your context needs to change to this:
public class ApplicationContext : DbContext
{
public DbSet<Apps> Apps { get; set; }
public DbSet<CallDetail> CallDetails { get; set; }
}
You could create a view model object called AppCallInfo that has three properties:
public class AppCallInfo
{
public int AppID { get; set; }
public string AppName { get; set; }
public int CallCount { get; set; }
}
In your Index action you need to do something like this:
public ViewResult Index()
{
var model = from a in db.Apps
join c in db.CallDetails on a.AppID equals c.AppID
where c.Enabled == enabled
group a by new { AppName = a.AppName, AppID = a.AppID } into g
select new AppCallInfo {
AppName = g.Key.AppName,
AppID = g.Key.AppID,
CallCount = g.Count()
};
return View(model.ToList());
}
Now you have everything you need for each row in your table in one object.
#model List<myMessagePlayer.ViewModels.AppCallInfo>
...
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.AppName)
</td>
<td class="appLink">
#Html.ActionLink(item.CallCount, "ViewCalls", "Call", new { Id = item.AppID }, null)
</td>
</tr>
}
Using this method avoids hitting the database for each app you have in your table.
Is the view CallCheck a partial view?
In your index view you could use
#Html.RenderAction("CallCheck", "AppsController", new { Id = #Model.AppId } )
The syntax may not be 100% correct, but it should get you going in the right direction.
Related
I have a subscription form that contains a matrix of options. The form can be seen in screenshot Subscription table
I am having trouble with ASP.NET MVC generating appropriate ID's and then on postback having the binder populate the model with the form selections.
The add on name is down the left side and when posted back the collection of SubscriptionInputModel.Addons get populated ok. But SubscriptionInputModel.Addons[i].SubscriptionLevelCombos is null as seen in debug screenshot
The current code is using CheckBoxFor but I've also tried manually generating ID's in format:
#Html.CheckBox("addon[" + a + "].SubscriptionLevelCombo[" + i + "].AddonSelected", addon.SubscriptionLevelCombos[i].AddonSelected)
Neither format has worked and also experimented while debugging but no luck. I would appreciate any ideas. Worst case I assume I would need to read the raw form collection?
I assume the level of nested object shouldn't matter as it is all object path notation and array indexes in html tag names?
Here are snippets of current code to help illustrate what exists.
View Models
public class SubscriptionInputModel
{
//other stuff to come
//....
//add on's, listed down left of table
public List<SubscriptionInputAddonModel> Addons;
}
public class SubscriptionInputAddonModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Note { get; set; }
public List<SubscriptionInputAddonComboModel> SubscriptionLevelCombos { get; set; }
}
public class SubscriptionInputAddonComboModel
{
public int? Id { get; set; }
public decimal? AddonCost { get; set; }
public CostTimeUnitOption? CostTimeUnit { get; set; }
public bool? IsComplimentaryBySubscriptionLevel { get; set; }
public string ComboText { get; set; }
public bool AddonSelected { get; set; }
public int? AddonId { get; set; }
}
SubscriptionController
[Route("identity/subscription")]
// GET: Subscription
public ActionResult Index()
{
SubscriptionInputModel model = new SubscriptionInputModel();
ArrbOneDbContext db = new ArrbOneDbContext();
List<SubscriptionInputAddonModel> addons = Mapper.Map<Addon[], List<SubscriptionInputAddonModel>>(db.Addons.OrderBy(a => a.OrderPosition).ToArray());
model.Addons = addons;
foreach(var addon in model.Addons)
{
var addonCombos = db.Database.SqlQuery<SubscriptionInputAddonComboModel>(#"SELECT SLA.Id, AddonCost, CostTimeUnit, IsComplimentaryBySubscriptionLevel, ComboText, AddonId
FROM SubscriptionLevel L
LEFT OUTER JOIN SubscriptionLevelAddon SLA ON L.Id = SLA.SubscriptionLevelId AND SLA.AddonId = #p0
ORDER BY L.OrderPosition", addon.Id);
addon.SubscriptionLevelCombos = addonCombos.ToList();
}
return View(model);
}
[Route("identity/subscription")]
[ValidateAntiForgeryToken]
[HttpPost]
// POST: Subscription
public ActionResult Index(SubscriptionInputModel model)
{
ArrbOneDbContext db = new ArrbOneDbContext();
List<SubscriptionInputAddonModel> addons = Mapper.Map<Addon[], List<SubscriptionInputAddonModel>>(db.Addons.OrderBy(a => a.OrderPosition).ToArray());
model.Addons = addons;
//debug breakpoint to inspect returned model values
return View();
}
Index.cshtml
#model Identity_Server._Code.ViewModel.Subscription.SubscriptionInputModel
#{
ViewBag.Title = "Subscription";
}
#using (Html.BeginForm("Index", "Subscription", new { signin = Request.QueryString["signin"] }, FormMethod.Post))
{
#Html.ValidationSummary("Please correct the following errors")
#Html.AntiForgeryToken()
...
// ADD ONs ----------------------------------------------------------------------------------
#for (int a = 0; a < Model.Addons.Count; a++)
{
var addon = Model.Addons[a];
<tr>
<td class="text-left">#addon.Name
<div class="SubscriptionItemNote">#addon.Note
#Html.HiddenFor(m => m.Addons[a].Id)
</div>
</td>
#for (int i = 0; i < addon.SubscriptionLevelCombos.Count; i++)
{
<td>
#if (addon.SubscriptionLevelCombos[i].Id.HasValue)
{
if (addon.SubscriptionLevelCombos[i].AddonCost.HasValue && addon.SubscriptionLevelCombos[i].AddonCost.Value > 0)
{
#Html.Raw("<div>+ " + #addon.SubscriptionLevelCombos[i].AddonCost.Value.ToString("0.##") + " / " + #addon.SubscriptionLevelCombos[i].CostTimeUnit.Value.ToString() + "</div>")
}
else if (addon.SubscriptionLevelCombos[i].IsComplimentaryBySubscriptionLevel.HasValue && #addon.SubscriptionLevelCombos[i].IsComplimentaryBySubscriptionLevel.Value)
{
<span class="glyphicon glyphicon-ok"></span>
}
if (!string.IsNullOrEmpty(addon.SubscriptionLevelCombos[i].ComboText))
{
<div>#addon.SubscriptionLevelCombos[i].ComboText</div>
}
if (addon.SubscriptionLevelCombos[i].AddonCost.HasValue && addon.SubscriptionLevelCombos[i].AddonCost.Value > 0)
{
#Html.HiddenFor(m => m.Addons[a].SubscriptionLevelCombos[i].Id)
#Html.CheckBoxFor(m => m.Addons[a].SubscriptionLevelCombos[i].AddonSelected)
}
}
</td>
}
</tr>
}
I have searched around and not had much luck finding a solution to my exact problem.
Model
public class PageDetailsViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Image { get; set; }
}
Controller
public ActionResult Search(int SysID)
{
var query = from r in _db.Auctions
from d in _db.Product_Details
where SysID == d.Id && r.BidStatus == "Open" && d.Id == r.Product_DetailsId
select new PageDetailsViewModel
{
Name = d.Name,
Description = d.Description,
Image = d.Image
};
return View(query);
}
View
#model IEnumerable<ProjectT.Models.PageDetailsViewModel>
#Html.DisplayNameFor(x => x.Name)
This fails to bring the name through. However, if I use a foreach
#foreach (var item in Model)
{
#item.Name
}
It brings through the name no problem.
Any help is much appreciated.
This extension method shows the value of the DisplayNameAttribute from DataAnnotations namespace. Consider this a label. Typically it is used like this:
[DisplayName("The Name")]
public string Name { get; set; }
And in the view:
#Html.DisplayNameFor(x => x.Name) <-- displays The Name
The code above will work only if the model is a single item. For the list case, as you have, you need to do some tricks, say a for loop, so you could do something like:
#Html.DisplayNameFor(x => x[i].Name): #Model[i].Name <-- displays The Name: Bill
I have one model called ProductSupplier
I am passing #model IEnumerable to my View
and showing it from view
Now when i submit the form i m not getting list of IEnumerable in my http post method. I want to know the selected supplier from user.
Below is my model
public sealed class ProductSupplier
{
public int CountryId { get; set; }
public int UserId { get; set; }
public bool IsProductSupplier { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
This is my HttpGet method
public ActionResult ManageSupplier(int id)
{
var supplier = App.UsersRepo.GetSupplierForProduct(id);
return View(supplier);
}
And I am binding it via following way (U can suggest me best way I am new bee to MVC)
#model IEnumerable<ProductSupplier>
#using (Html.BeginForm("ManageSupplier", "Products", FormMethod.Post, new { role = "form") })
{ #Html.AntiForgeryToken()
foreach (var item in Model)
{
<div class="checkbox">
<label>
#Html.CheckBoxFor(x => item.IsProductSupplier, new { id = item.Email }) #item.FirstName #item.LastName (#item.Email)
</label>
</div>
}
}
And finally my HttpPost method
[HttpPost]
public ActionResult ManageSupplier(IEnumerable<ProductSupplier> obj)
{ // I m getting obj null in my argument
//I want to Get selected id from obj and want to pass in selectedSupplier
var returnVal = App.ProductRepo.AssigneSupplierForProduct(productId, selectedSupplier);
return Json(new { success = true }, JsonRequestBehavior.DenyGet);
}
can anyone suggest me where i m making mistake.
I am new to MVC any kind of suggestion highly appreciated.
Thank you in advance.
Firstable u cant do it like this.One way to do that is something like this.Here is the basic step how u do that.
1-assign for all checkbox ,checkbox change event with the unique id.
(take a look at here)
2-Cretae a jquery object and store the data when ever the checkbox clicked ,via versa
var ListProductSuppliers ={ {ProductSupplier_info_here },{ProductSupplier_info_here } };
3-later via ajax request,serilize this object(ListProductSuppliers ) and send to your method
4-on server side deserilize this to the IEnumerable<ProductSupplier>
5 later do it whatever u want with those selected suppliars
Please bear with my noobness, I'm super new to the MVC pattern.
What I'm trying to do
I am building a profile information page for registered users on my site. This page would list data about the user, such as date of birth, telephone number, subscription status, etc.. You get the idea. I would also like to have a form to let users change their password, email address, personal information on the same page.
My problem
The user's data comes from my controller via a passed model variable:
public ActionResult Profil()
{
var model = db.Users.First(e => e.UserName == WebSecurity.CurrentUserName);
return View(model);
}
The output looks like this in my view:
<label>Phone number: </label>
#if (Model.PhoneNumber != null)
{
#Model.PhoneNumber
}
else
{
<span class="red">You haven't set up your phone number yet. </span>
}
The form in which the user could change his info would use another model, ProfileModel. So basiccaly I need to use two models in my view, one for outputting information and one for posting data. I thought that using a partial view I can achieve this, but I get this error:
The model item passed into the dictionary is of type
'Applicense.Models.User', but this dictionary requires a model item of
type 'Applicense.Models.ProfileModel'.
Here's what my call to the partial view looks like:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
#Html.Partial("_ModifyProfileInfo")
}
Here's the partial view:
#model Applicense.Models.ProfileModel
<ul>
<li>
#Html.LabelFor(m => m.Email)
#Html.EditorFor(m => m.Email)
</li>
<li>
#Html.LabelFor(m => m.ConfirmEmail)
#Html.EditorFor(m => m.ConfirmEmail)
</li>
<input type="submit" value="Update e-mail" />
</ul>
And finally here's my ProfileModel:
public class ProfileModel
{
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "New e-mail address")]
public string Email { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name = "Confirm new e-mail address")]
[Compare("Email", ErrorMessage = "The e-mail and it's confirmation field do not match.")]
public string ConfirmEmail { get; set; }
}
Am I missing something? What's the proper way to do this?
Edit:
I remade my code reflecting Nikola Mitev's answer, but now I have another problem. Here's the error I get:
Object reference not set to an instance of an object. (#Model.UserObject.LastName)
This only occurs when I'm posting the changed e-mail address values. Here's my ViewModel (ProfileModel.cs):
public class ProfileModel
{
public User UserObject { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Új e-mail cím")]
public string Email { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name = "Új e-mail cím megerősítése")]
[Compare("Email", ErrorMessage = "A két e-mail cím nem egyezik.")]
public string ConfirmEmail { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name= "E-mail cím")]
public string ReferEmail { get; set; }
}
Controller:
public ActionResult Profil()
{
var User = db.Users.First(e => e.UserName == WebSecurity.CurrentUserName);
var ProfileViewModel = new ProfileModel
{
UserObject = User
};
return View(ProfileViewModel);
}
And finally here's my user.cs model class:
[Table("UserProfile")]
public class User
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[Column("UserName")]
public string UserName { get; set; }
[Column("Email")]
[Required]
public string Email { get; set; }
[Column("FirstName")]
public string FirstName { get; set; }
[Column("LastName")]
public string LastName { get; set; }
[Column("PhoneNumber")]
public string PhoneNumber { get; set; }
... You get the idea of the rest...
I'm thinking it's happening because the model is trying to put data in each required columns into the database.
Edit2:
The httppost method of my Profil action:
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult Profil(ProfileModel model)
{
if (ModelState.IsValid)
{
//insert into database
return Content("everything's good");
}
else
{
//outputs form errors
return View(model);
}
}
The best way to handle this situation is to use and pass viewModel to your Profile controller, viewModel is wrapper class for multiple objects that you want to pass to your view.
public class ProfileUserViewModel
{
public ProfileModel ProfileModelObject {get; set;}
public UserModel UserModelObject {get; set;}
}
Your controller should look like:
public ActionResult Profil()
{
var profileModel = db.Users.First(e => e.UserName == WebSecurity.CurrentUserName);
var userModel = //fetch from db.
var pmViewModel = new ProfileUserViewModel
{
ProfileModelObject = profileModel,
UserModelObject = userModel
};
return View(pmViewModel);
}
And finally your view :
#model Applicense.Models.ProfileUserViewModel
<label>Phone number: </label>
#if (Model.ProfileModelObject.PhoneNumber != null)
{
#Model.PhoneNumber
}
else
{
<span class="red">You haven't set up your phone number yet. </span>
}
There is an overload of #Html.Partial which allows you to send ViewData as defined in your controller - this is the method I generally use for partial views.
In your controller define ViewData["mypartialdata"] as ViewDataDictionary. Then in your view
#Html.Partial("_ModifyProfileInfo",ViewData["mypartialdata"])
In your [HttpPost] profil function, if modelstate.isvalid is false, you return your edit view, but you need to define your pmViewModel again , other wise your partial view will not have an object to display. Try using the following and let us know what happens
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult Profil(ProfileModel model)
{
if (ModelState.IsValid)
{
//insert into database
return Content("everything's good");
}
else
{
//outputs form errors
var pmViewModel = new ProfileUserViewModel
{
ProfileModelObject = profileModel,
UserModelObject = userModel
};
return View(model);
}
}
While I know this question has been asked longtime ago however some people might still face a similar problem. One easy solution I use to pass or have more than one view model on a page is to use a ViewBag to hold the second object and refer to it in the view. See example bellow.
In your controller do this:
Obj2 personalDets = new Obj2();
DbContext ctx = new DbContext();
var details = ctx.GetPersonalInformation;
foreach(var item in details) {
personalDets.Password = item.Password;
personalDets .EmailAddress = item.EmailAddress;
}
ViewBag.PersonalInformation = personalDets;
Then in your view those properties become readily available for you
I created a page with aspnet mvc3. It show all users info as a list. I want to do something with this list. There are some checkboxes that belong to each items. When I click some checkboxes and press submit button, I want to post the whole list as a collection and save each items of this collection to database. There are several notes on internet but there is no exact solution. I have a UserDto. and want to use this to transfer users data in all sections.
Does anyone have any full solution about this or can they give any idea?
Thanks in advance.
Kerem
I added some of my codes. You can see the lead sentences what they are about.
this is my index view detail:
#model List<DomainModel.UserApprovalDto>
#{
ViewBag.Title = "Manage Users";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Manage Users</h2>
<div>#Html.Partial("_PartialManageUsers", (List<DomainModel.UserApprovalDto>)Model) </div>
this is my partial view detail:
#model List<DomainModel.UserApprovalDto>
#using (Html.BeginForm("ConfirmUsers", "ManageUsers", FormMethod.Post))
{
<table>
<tr>
<th>
Name
</th>
<th>
Is Reported
</th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].FirstName)
</td>
<td>
#Html.CheckBox("IsReported", Model[i].IsReported.HasValue ? Model[i].IsReported.Value : false)
#*#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);*# #* #if (Model[i].IsReported != null)
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}
else
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}*#
</td>
<td>
</td>
</tr>
}
</table>
<div>
<input name="submitUsers" type="submit" value="Save" />
</div>
}
this is my controller submit method
[HttpPost]
public ActionResult ConfirmUsers(List<DomainModel.UserApprovalDto> collection)
{
if (ModelState.IsValid)
{
//TO-DO
}
return RedirectToAction("Index");
}
this last one is my DTO class detail:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DomainModel
{
public class UserApprovalDto
{
public long UserId { get; set; }
public Guid CarUserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhotoPath { get; set; }
public string PhotoSmallPath { get; set; }
public string PhotoSquarePath { get; set; }
public string PhotoBigPath { get; set; }
public bool IsBlocked { get; set; }
public bool IsDeleted { get; set; }
}
}
when I submit this code my list return null collection to my controller method.
thanks for your comments.
Assuming you are creating a screen which adds/ remove users to a course. So let's create some viewmodels
public class CourseVM
{
public string Name { set;get;}
public int CourseID { set;get;}
public List<UserVM> Users { set;get;}
public CourseVM()
{
Users=new List<UserVM>();
}
}
public class UserVM
{
public string Name { set;get;}
public int UserID{ set;get;}
public bool IsSelected { set;get;}
}
Now in your GET Action, you will fill the values of the ViewModel and sent it to the view.
public ActionResult Add()
{
var vm = new CourseVM();
//The below code is hardcoded for demo. you may replace with DB data.
vm.Users.Add(new UseVM { Name = "Jon" , UserID=1});
vm.Users.Add(new UseVM { Name = "Scott", UserID=2 });
return View(vm);
}
Now Let's create an EditorTemplate. Go to Views/YourControllerName and Crete a Folder called "EditorTemplates" and Create a new View there with the same name as of the Property Name(UserVM.cshtml)
Add this code to your new editor template.
#model ChannelViewModel
<p>
<b>#Model.Name</b> :
#Html.CheckBoxFor(x => x.IsSelected) <br />
#Html.HiddenFor(x=>x.Id)
</p>
Now in your Main View, Call your Editor template using the EditorFor Html Helper method.
#model CourseVM
#using (Html.BeginForm())
{
<div>
#Html.EditorFor(m=>m.Users)
</div>
<input type="submit" value="Submit" />
}
Now when you post the form, Your Model will have the Users Collection where the Selected Checkboxes will be having a True value for the IsSelected Property.
[HttpPost]
public ActionResult Add(CourseVM model)
{
if(ModelState.IsValid)
{
//Check for model.Users collection and Each items
// IsSelected property value.
//Save and Redirect(PRG pattern)
}
return View(model);
}