I'm having trouble with one particular issue, I was hoping someone could help me out.
I've completed the MVC Music Store tutorial, and now I'm trying to add some administrator functionality - practice as I will have to do this in an MVC application in my job. The application is using the aspnet membership api, and what I have done so far is created a view to list the users.
What I want to be able to do, is click on the users name in order to change their password. To try and carry the username to the changeUserPassword controller (custom made). I registered a new route in the global.asax.cs file in order to display the username in the URL, which is working so far.
UserList View
<%: Html.RouteLink(user.UserName, "AdminPassword", new { controller="StoreManager", action="changeUserPassword", username = user.UserName }) %>
Global.asax.cs
routes.MapRoute(
"AdminPassword", //Route name
"{controller}/{action}/{username}", //URL with parameters
new { controller = "StoreManager", action = "changeUserPassword", username = UrlParameter.Optional}
);
So now the URL looks like this when I reach the changeUserPassword view:
http://localhost:51236/StoreManager/changeUserPassword/Administrator
Here is the GET changeUserPassword action:
public ActionResult changeUserPassword(string username)
{
ViewData["username"] = username;
return View();
}
I wanted to store the username in ViewData as I would like to use it in the GET changeUserPassword for display purposes, and also as a hidden value in the form. This is in order to pass it through to enable me to reset the password.
Having debugged through the code, it seems that 'username' is null.
How can I get this to work so that the username carries over from the Html.RouteLink, to the changeUserPassword action?
Any help would be appreciated :)
Here is my complete code:
UserList.aspx
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<System.Web.Security.MembershipUserCollection>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
UserList
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>UserList</h2>
<table>
<tr>
<th>User Name</th>
<th>Last Activity date</th>
<th>Locked Out</th>
</tr>
<%foreach (MembershipUser user in Model){ %>
<tr>
<td><%: Html.RouteLink(user.UserName, "AdminPassword", new { controller="StoreManager", action="changeUserPassword", username = user.UserName }) %></td>
<td><%: user.LastActivityDate %></td>
<td><%: user.IsLockedOut %></td>
</tr>
<% }%>
</table>
</asp:Content>
changeUserPassword.aspx
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<musicStoreMVC.ViewModels.ResetPasswordAdmin>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
changeUserPassword
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Change Password: <%: ViewData["username"] %></h2>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%: Html.Hidden("username",ViewData["username"]) %>
<%: Html.LabelFor(model => model.password) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.password) %>
<%: Html.ValidationMessageFor(model => model.password) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.confirmPassword) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.confirmPassword) %>
<%: Html.ValidationMessageFor(model => model.confirmPassword) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%: Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
My actions
public ActionResult UserList()
{
var users = Membership.GetAllUsers();
return View(users);
}
public ActionResult changeUserPassword(string username)
{
ViewData["username"] = username;
return View();
}
Something must be going wrong with your routes.
If you navigate to this URL, and set a breakpoint in the changeUserPassword action method - you'll probable see the username value correctly:
http://localhost:51236/StoreManager/changeUserPassword?username=Administrator
Related
How do I create a view to add a new item to a list in a view model?
So I have view model with a list of objects in it (view model also has 1 other object in it). Basically the list will be comments (so I may have zero to x number of comments).
I am using VS 2010 (and I don't think it is the razor view engine?)
Here is my view, I want to have the option to add a new item to the list
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<JPROCommunitydataListings.ViewModels.dataWithCommentsViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
data Solution
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<p>data Description# <%: Model.data.Description %></p>
<p>data ParameterID# <%: Model.data.ParameterID %></p>
<%--<h2>dataWithComments</h2>--%>
<table class="table table-striped table-hover">
<tr>
<th>
Comment
</th>
</tr>
<% foreach (var Comment in Model.Comments) { %>
<td>
<%: Comment.comment1%>
</td>
<% } %>
</table>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Add Comment</legend>
<div class="editor-label">
<"label">
What to do here????????
<%--<%: Html.LabelFor(model => model.Comments[0].comment1) %>--%>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Comments.comment1)%>
<%: Html.ValidationMessageFor(model => model.Comments[0].comment1)%>
</div>
What to do here????????
<p>
<input type="submit" value="Search" />
</p>
</fieldset>
<% } %>
</fieldset>
<p>
<%: Html.ActionLink("Search", "Search") %>
</p>
</asp:Content>
"What to do here"
You need a form:
<form action="/ControlerName/AddComment" method="POST">
...
<%: Html.TextBox("CommentToAdd")%>
<%: Html.Hidden("PostIdToAddComment")%>
<input type="submit" value="Search" />
</form>
In controller you save your comment to DB, then redirect back to the original action that will display the page, and so the comment should show up assuming you are querying the database for all comments:
[HttpPost]
public ActionResult AddComment(string commentToAdd, int postIdToAddComment)
{
//do your database stuff here to add comment
return RedirectToAction("DataWithCommentsOrWhateverTheNameOfTheActionIsForTheOriginalPage")
}
<%# Page Language="C#" MasterPageFile="~/Views/Shared/MasterPage.Master" Inherits="System.Web.Mvc.ViewPage<EAZYITT_LOGIN.Models.CombinedViewModel>" %>
<asp:Content ContentPlaceHolderID="TitleContent" runat="server">
LoginPage
</asp:Content>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<div id="LoginWindow">
<% Html.RenderPartial("LoginWindow", ViewData.Model.Logon); %>
<a id="ForgetPassword" href="#" onclick="loadSegment()">Forgot Password</a>
</div>
<div id="PassReminderWindow">
<% Html.RenderPartial("ReminderWindow", ViewData.Model.Reminder); %>
</div>
</asp:Content>
Each partial view is strongly typed with separate postbacks to the server
Login:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<EAZYITT_LOGIN.Models.LogOnModel>" %>
<div id="loginPage">
<h2>Login Page</h2>
<h3>Submit your credentials to continue or register</h3>
<%: Html.ActionLink("New Registration", "Register")%>
<%: Html.ValidationSummary(true)%>
<%: Html.ValidationSummary()%>
<div class="validation-summary-errors">
<span id="loginError"></span>
</div>
<% using (Html.BeginForm("LoginWindow","Account",FormMethod.Post)) { %>
<%:Html.LabelFor(m =>m.EmailAddress) %>
<%:Html.ValidationMessageFor(m => m.EmailAddress) %>
<%:Html.TextBoxFor(m => m.EmailAddress) %>
<%:Html.LabelFor(m =>m.Password) %>
<%:Html.ValidationMessageFor(m => m.Password) %>
<%:Html.PasswordFor(m => m.Password)%>
<%:Html.CheckBoxFor(m => m.RememberMe)%>
<%:Html.LabelFor(m => m.RememberMe)%>
<p>
<input type="submit" value="Log On" />
</p>
<% } %>
</div>
Password Reminder:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<EAZYITT_LOGIN.Models.ReminderModel>"%>
<div id="PasswordReminderDiv">
<h2>PasswordReminder</h2>
<%: Html.ValidationSummary(true) %>
<div class="validation-summary-errors"><span
id="reminderError"></span></div>
<% using (Html.BeginForm("PasswordReminder",
"Account",FormMethod.Post))
{ //'8' o-o +:: %>
<%:Html.LabelFor(m=>m.ReminderEmailAddress) %>
<%:Html.ValidationMessageFor(m =>
m.ReminderEmailAddress)%>
<%:Html.TextBoxFor(m => m.ReminderEmailAddress) %>
<p>
<input type="submit" value="Send Reminder" />
</p>
<%} %>
</div>
The results are submitted to their separate methods in the controller:
[HttpGet]
public ActionResult Login()
{
CombinedViewModel cModel = new CombinedViewModel();
cModel.Logon = new LogOnModel();
cModel.Reminder = new ReminderModel();
return View(cModel);
}
[HttpPost]
public ActionResult Login(CombinedViewModel _login)
{
return View(_login);
}
[HttpGet]
public ActionResult LoginWindow()
{
return PartialView();
}
[HttpPost]
public ActionResult LoginWindow(LogOnModel _login)
{
if (ModelState.IsValid)
{
if (LoginService.ValidateUser(siteId, _login.EmailAddress, _login.Password))
{
//Goto Next Page
ModelState.AddModelError("loginError", "LOGIN - OK");
}
else
{
//Failed Login
ModelState.AddModelError("loginError", "Wrong username or password");
}
}
return PartialView("LoginWindow", _login);
}
[HttpGet]
public ActionResult PasswordReminder()
{
return View();
}
[HttpPost]
public ActionResult PasswordReminder(ReminderModel _reminder)
{
TempData["ModelState"] = ModelState;
if (LoginService.ValidateNewUser(siteId, _reminder.ReminderEmailAddress))
ModelState.AddModelError("reminderError", "The E-mail address does not exist");
if (ModelState.IsValid)
{
ModelState.AddModelError("reminderError", "E-mail found, send e-mail to user");
}
return PartialView("PasswordReminder",_reminder);
}
Ideally, I would like the main view (Login.aspx) to be displayed regardless of the validation.
However, I've currently got the validation on each partial view working, BUT it is taking me to their separate partial views on failed validation as opposed to the main view.
How would I get it to update the partial view only? Is this the way to do it or should I be using AJAX?
The only way to update the partial view only is AJAX, use AJAX.BeginForm or use JQuery. Otherwise, it expects to go through the full lifecycle.
HTH.
I have the following code that was generated using scaffolding and IDJefe is an int in my database, but I want the end users to choose a name from a comboBox.
How could I accomplish this?
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<SeguimientoDocente.Area>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
UTEPSA | Editando Area
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Editando Area: <%: Model.Nombre %></h2>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Informacion Detallada de Area | <%: Model.Nombre %></legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.Nombre) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Nombre) %>
<%: Html.ValidationMessageFor(model => model.Nombre) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.IDJefe) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.IDJefe) %>
<%: Html.ValidationMessageFor(model => model.IDJefe) %>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%: Html.ActionLink("Volver a Listado General", "Index") %>
</div>
</asp:Content>
I've tried the following to no avail.
<%: Html.DropDownList(Model.Jefes???? %>
I could do something like this, but creating a new object for a simple thing like this seems a waste.
public ActionResult Edit(int id)
{
Area area = areaRepository.GetArea(id);
JefeRepository jefe = new JefeRepository();
ViewData["Jefes"] = new SelectList(jefe.FindAllJefes(), area.Jefe.Nombre);
return View(area);
}
Is there a better way?
You could take a look at Editor Templates. Here is an example that sounds similar to what you want to do:
http://blogs.msdn.com/b/nunos/archive/2010/02/08/quick-tips-about-asp-net-mvc-editor-templates.aspx
Edit:
It involves creating a partial view and then using Data Annotations to call that view:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<%= Html.DropDownList("",new SelectList((string[]) ViewData["Ratings"],Model)) %>
How do I debug code in the View in asp.net mvc2 application?
Edit after progress last night:
Ok so now I have the following:
in Shared\EditorTemplates\Equipment.ascx :
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DAT.Models.Item>" %>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<div class="editor-label">
<%: Html.Label("Item ID") %>
<%: Html.TextBoxFor(model => model.ItemID) %>
<%: Html.ValidationMessageFor(model => model.ItemID) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.ModelID) %>
<%: Html.DropDownListFor(x => x.Model.Model1, new SelectList(Model.Model.Model1, "ModelId", "Model", Model.ModelID)) %>
<%: Html.ValidationMessageFor(model => model.ModelID) %>
</div>
...
in Item\Edit.aspx :
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<DAT.ViewModels.ItemEditViewModel>" %>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<%: Html.EditorFor(model => model.Item) %>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
The controller:
public ActionResult Edit(int id)
{
var models = from p in database.Models.Where(x => x.Model1 != null) select p;
var viewModel = new ViewModel
{
Things = database.Things.Single(a => a.ItemID == id),
//tried this:
//Models = database.Models
Models = models
};
return View(viewModel);
}
So I am sure that the problem is with this line
<%: Html.DropDownListFor(x => x.Model.Model1, new SelectList(Model.Model.Model1, "ModelId", "Model", Model.ModelID)) %>
When generating the selectlist, I don't have IEnumerable for the first parameter? Or one of the values I am feeding this is causing null. How do I get the list of models in my view?
EDIT AFTER PULLING ALL MY HAIR OUT:
It seems the problem lies in this example: http://www.asp.net/mvc/tutorials/mvc-music-store-part-4 . Strangely, I am not sure if it is following best practice. Look at the code and how they pass the models about - it seems stupidly obscure using ViewData["Blah"] and the Models as well, why can't you just have it all sent as the model? Look at the code how they have done it:
Album.ascx :
<%# Import Namespace="MvcMusicStore"%>
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcMusicStore.Models.Album>" %>
<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>
<p>
<%: Html.LabelFor(model => model.Title)%>
<%: Html.TextBoxFor(model => model.Title)%>
<%: Html.ValidationMessageFor(model => model.Title)%>
</p>
<p>
<%: Html.LabelFor(model => model.Price)%>
<%: Html.TextBoxFor(model => model.Price)%>
<%: Html.ValidationMessageFor(model => model.Price)%>
</p>
<p>
<%: Html.LabelFor(model => model.AlbumArtUrl)%>
<%: Html.TextBoxFor(model => model.AlbumArtUrl)%>
<%: Html.ValidationMessageFor(model => model.AlbumArtUrl)%>
</p>
<p>
<%: Html.LabelFor(model => model.Artist)%>
<%: Html.DropDownList("ArtistId", new SelectList(ViewData["Artists"] as IEnumerable, "ArtistId", "Name", Model.ArtistId))%>
</p>
<p>
<%: Html.LabelFor(model => model.Genre)%>
<%: Html.DropDownList("GenreId", new SelectList(ViewData["Genres"] as IEnumerable, "GenreId", "Name", Model.GenreId))%>
</p>
View:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcMusicStore.ViewModels.StoreManagerViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Edit - <%: Model.Album.Title %>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Edit Album</h2>
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Edit Album</legend>
<%: Html.EditorFor(model => model.Album, new { Artists = Model.Artists, Genres = Model.Genres}) %>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%:Html.ActionLink("Back to Albums", "Index") %>
</div>
</asp:Content>
View Model:
using System.Collections.Generic;
using MvcMusicStore.Models;
namespace MvcMusicStore.ViewModels
{
public class StoreManagerViewModel
{
public Album Album { get; set; }
public List<Artist> Artists { get; set; }
public List<Genre> Genres { get; set; }
}
}
And the controller:
//
// GET: /StoreManager/Edit/5
public ActionResult Edit(int id)
{
var viewModel = new StoreManagerViewModel
{
Album = storeDB.Albums.Single(a => a.AlbumId == id),
Genres = storeDB.Genres.ToList(),
Artists = storeDB.Artists.ToList()
};
return View(viewModel);
}
So it looks to me like the model is built in the controller as makes logical sense to me. Then in the view they use this statement which causes my confusion:
<%: Html.EditorFor(model => model.Album, new { Artists = Model.Artists, Genres = Model.Genres}) %>
The model is being split/changed and now we send the other (Artists, Genres) as ViewData ??
Can someone explain, and is this at all fitting with the entire design pattern?
You could put a breakpoint in your controller action and analyze your model and view data.
This being said, why are you using a strongly typed view and ViewData at the same time? Make sure that ViewData["Models"] as IEnumerable is not null (in your controller) or even better get rid of it and put it in your model as a strongly typed property. Also I would recommend you using the strongly typed helper DropDownListFor:
<%: Html.DropDownListFor(
x => x.ModelID,
new SelectList(Model.Models, "ModelId", "Model", Model.ModelID)
)%>
In C# ASP.NET MVC application I use Link to SQL to provide data for my application. I have got simple database schema like this:
In my controller class I reference this data context called Model (as you can see on the right side of picture in properties) like this:
private Model model = new Model();
I've got a table (List) of Series rendered on my page. It renders properly and I was able to add delete functionality to delete Series like this:
public ActionResult Delete(int id) {
model.Series.DeleteOnSubmit(model.Series.SingleOrDefault(s => s.ID == id));
model.SubmitChanges();
return RedirectToAction("Index");
}
Where appropriate action link looks like this:
<%: Html.ActionLink("Delete", "Delete", new { id=item.ID })%>
Also create (implemented in similar way) works fine. However edit does not work. My edit looks like this:
public ActionResult Edit(int id) {
return View(model.Series.SingleOrDefault(s => s.ID == id));
}
[HttpPost]
public ActionResult Edit(Series series) {
if (ModelState.IsValid) {
UpdateModel(series);
series.Title = series.Title + " some string to ensure title has changed";
model.SubmitChanges();
return RedirectToAction("Index");
}
return View(series);
}
I have controlled that my database has a primary key set up correctly. I debugged my application and found out that everything works as expected until the line with model.SubmitChanges();. This command does not apply the changes of Title property(or any other) against the database.
Please help.
EDIT:
If I add this line: model.Series.Attach(series); just before model.SubmitChanges(); there is no change - edits still does not reflect to database. The instance passed to Edit method as a parameter is already attached to the data context model.
EDIT:
Code of view that belongs to method Edit:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<TVSeriesInfoApp.Models.Series>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Edit
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Edit</h2>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.Title) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Title) %>
<%: Html.ValidationMessageFor(model => model.Title) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Seasons) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Seasons) %>
<%: Html.ValidationMessageFor(model => model.Seasons) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Stars) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Stars) %>
<%: Html.ValidationMessageFor(model => model.Stars) %>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%: Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
This is what our Edit Action would look like (Adjusted for your model):
[HttpPost]
public ActionResult Edit(int id, Series series)
{
Series updatingSeries = model.Series.Single(s => s.ID == id);
try
{
TryUpdateModel(updatingSeries);
model.SubmitChanges();
return RedirectToAction("Details", new { id = updatingSeries.ID });
}
catch
{
return View(updatingSeries);
}
}
This can happen because the ModelState might not be valid in some cases. Have you done something to the View? Can you also post your View Code here please?
First, never ever "delete" with HTTP GET (this is exactly what you're doing with Html.ActionLink("Delete", "Delete", new { id=item.ID }).
As for edits, you first have to Attach your instance to the DataContext.