Using a mixture of attribute named routing and standard conventional routing.
I have a UserController with two 'Create' actions. One is a HTTPGet for getting the view, and one is a HTTPPost for posting the data back.
Here is that entire controller:
[RoutePrefix("User")]
public class UserController : Controller {
public UserController() {}
[HttpGet]
public ActionResult Create() {
return View();
}
[Route("Create")]
[HttpPost]
public async Task<ActionResult> CreateAsync(UserM m) {
if (!ModelState.IsValid) {
return View(m);
}
var user = new User() {
Email = m.Email,
Forename = m.Forename,
IsActive = true,
Surname = m.Surname
};
return Redirect("");
}
}
However when I try to navigate to User/Create from the submit button on the site, to post the data back, I get a 404.
Here is the route config:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Turn on attribute routing in the controllers
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Here is the view that posts the data back:
#model MyApp.WebMS.Models.UserM
#{
ViewBag.Title = "Create User";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="container">
<div class="row">
<div class="xs-12">
<h3>Create User</h3>
<hr class="no-top-margin" />
</div>
</div>
#using (Html.BeginForm()) {
<div class="row">
<div class="col-sm-4">
<h4>Personal Information</h4>
<div class="user-creation-section">
<div class="form-group">
#Html.LabelFor(m => m.Forename, new { #class = "form-label" })
#Html.TextBoxFor(m => m.Forename, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Surname, new { #class = "form-label" })
#Html.TextBoxFor(m => m.Surname, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "form-label" })
#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
</div>
</div>
</div>
<div class="col-sm-4">
<h4>Job Information</h4>
<div class="user-creation-section">
<div class="form-group">
#Html.LabelFor(m => m.JobTitle, new { #class = "form-label" })
#Html.TextBoxFor(m => m.JobTitle, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.StartDate, new { #class = "form-label" })
#Html.TextBoxFor(m => m.StartDate, new { type = "date", #class = "form-control" })
</div>
</div>
</div>
</div>
<div class="row">
<div class="xs-12 pull-right">
<input type="button" class="btn" value="Back" onclick="location.href='#Url.Action("Index", "Home")'" />
<button type="submit" class="btn btn-primary">Create</button>
</div>
</div>
}
</div>
Since your POST action is named CreateAsync, you'll have to explicitly mention that in your form.
#using (Html.BeginForm("CreateAsync", "User", FormMethod.Post))
Related
I have a problem with my controller. It accepts null.
First of all I've got such ViewModel
public class FilmVM
{
public Film Film { get; set; }
public IEnumerable<Image> Images { get; set; } = new List<Image>();
public IEnumerable<SimilarFilm> SimilarFilms { get; set; } = new List<SimilarFilm>();
}
Controller looks like
public ActionResult Edit(FilmVM film)
{
_unitOfWork.Film.Update(film);
return RedirectToAction("Index");
}
Index action looks like
public ActionResult Index(int? page)
{
int pageSize = Constaints.Constaint.FilmsCount;
int pageNumber = page ?? 1;
var films = _unitOfWork.Film.GetDatas();
return View(films.ToPagedList(pageNumber, pageSize));
}
Models in VM are defaults like Guid, name, etc.
View model looks like
#model GuessTheMovieApp.ViewModels.FilmVM
#{
ViewBag.Title = "Edit";
}
<div class="wrapper">
<h2>Edit</h2>
#using (Html.BeginForm("Edit", "Films", FormMethod.Post, new { #class = "form-edit-film" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Editing Film</h4>
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.Partial("EditFilm", Model.Film)
<div class="form-group">
<div class="form-group__input">
<input type="submit" value="Save" class="btn btn-success" />
</div>
</div>
</div>
}
</div>
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Partial View looks like
#model GuessTheMovieApp.Models.DataBase.Film
<div class="form-horizontal">
<h4>Film</h4>
<hr />
#Html.HiddenFor(model => model.FilmId)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Year, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Year, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Year, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Genre, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Genre, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Genre, "", new { #class = "text-danger" })
</div>
</div>
</div>
they are common, I think.
I saw similar question on stackoverflow, but it didn't helped me. So I'm asking if someone knows an answer on such question, please help.
UPD
Figured out how to work with this by changing Action Edit to this format
public ActionResult Edit(Film films, IEnumerable<Image> images, IEnumerable<SimilarFilm> similarFilms)
{
_unitOfWork.Film.Update(new FilmVM { Film = films, Images = images, SimilarFilms = similarFilms });
_unitOfWork.Save();
return RedirectToAction("Index");
}
I don't think you can prevent your controller from accepting null at the parameter level, the best you can do is to do a null check on the object coming in before you call your _unitofWork
try to use the same model for view, partial view and action. You can use FilmVm as well as any another model. For example you can use Film in this case
#model GuessTheMovieApp.ViewModels.Film
.....
<partial name="EditFilm" />
......
and action
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Film film)
{
var filmVm= new FilmVm { Film=film}
.....
}
When I pass the view model to the method it crashes but it works with FormCollection or not passing anything to the onpost method.
[HttpPost]
[AutoValidateAntiforgeryToken]
public ActionResult Update(CustomerInformation model)
{
if (ModelState.IsValid)
{
//save
var UpdateRecord = customerServices.Update(model);
if (UpdateRecord)
{
return RedirectToAction("Details");
}
}
}
#model Customer.Models.CustomerInformationDetails
#using Customer.Models.CustomerInformation
#{
var customerName = Model.Name;
ViewData["Title"] = customerName;
}
<h1>Edit information for #customerName</h1>
<hr />
#using (Html.BeginForm("Update",
"Customer",
FormMethod.Post))
{
<div class="form-group">
#Html.Label("Introduction", "Introduction:", htmlAttributes: new { #class = "control-label col-md-10" })
<div class="col-md-10">
#Html.EditorFor(model => model.Introduction, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.Label("Contact Person", "Contact Person:", htmlAttributes: new { #class = "control-label col-md-10" })
<div class="col-md-10">
#Html.EditorFor(model => model.ContactPerson, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<input type="submit" value="Update" class="btn btn-primary"></input>
</div>
}
Add
return View(model)
outside of your if statement.
Currently if it fails it has nothing to return.
On my page I want to include a list of members in a drop down list but I am not sure how exactly I could do this.
How would I populate a drop down list with the members that I am passing with the controller?
This is my controller
//Add Event
public ActionResult CreateEvent()
{
var members = db.ClubMembers.ToList();
return View(members);
}
//Add Event
[HttpPost]
public ActionResult CreateEvent(ClubEvent incomingEvent)
{
try
{
if (ModelState.IsValid)
{
using (var db = new UltimateDb())
{
db.ClubEvents.Add(incomingEvent);
db.SaveChanges();
}
return RedirectToAction("Index");
}
return View();
}
catch
{
return View();
}
}
This is the view I will be using
#model ultimateorganiser.Models.ClubEvent
#{
ViewBag.Title = "CreateEvent";
}
<h2>CreateEvent</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>ClubEvent</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#*Event Title*#
<div class="form-group">
#Html.LabelFor(model => model.EventTitle, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.EventTitle, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EventTitle, "", new { #class = "text-danger" })
</div>
</div>
#*Event Description*#
<div class="form-group">
#Html.LabelFor(model => model.EventDesc, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.EventDesc, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EventDesc, "", new { #class = "text-danger" })
</div>
</div>
#*Event Type*#
<div class="form-group">
#Html.LabelFor(model => model.eventType, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EnumDropDownListFor(model => model.eventType, htmlAttributes: new { #class = "form-control", #id = "dropdown" })
#Html.ValidationMessageFor(model => model.eventType, "", new { #class = "text-danger" })
</div>
</div>
#*Add People*#
#*<div class="form-group">
Add Members
<div class="col-md-10">
Drop Down List of members will go here
</div>
</div>*#
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
The best way to pass data between views and action methods are view models.We will go that way.
First create a new view model for the create view.
public class CreateEventVm
{
public string EventDescription {set;get;}
public List<SelectListItem> Members {set;get;}
public int MemberId {set;get;}
}
and in your GET action
public ActionResult Create()
{
var vm = new CreateEventVm();
var db= new UltimateDb();
vm.Members =db.Members.Select(s=> new SelectListItem
{ Value=s.Id.ToString(),
Text = s.Name
}).ToList();
return View(vm);
}
And your create razor view which is strongly typed to our new CreateEventVm
#model CreateEventVm
#using(Html.BeginForm())
{
<label>Description</label>
#Html.TextBoxFor(s=>s.EventDescription)
<label>Member</label>
#Html.DropDownListFor(s=>s.MemberId,Model.Members,"Select one")
<input type="submit" />
}
And in your HttpPost action method
[HttpPost]
public ActionResult Create(CreateEventVm model)
{
if(ModelState.IsValid)
{
using (var db = new UltimateDb())
{
var event = new ClubEvent();
event.EventDescription = model.EventDescription;
//Set other properties also from view model.
db.ClubEvents.Add(event);
db.SaveChanges();
}
// Redirect to another action after successful save (PRG pattern)
return RedirectToAction("SavedSuccessfully");
}
vm.Members =db.Members.Select(s=> new SelectListItem
{ Value=s.Id.ToString(),
Text = s.Name
}).ToList();
return View(vm);
}
I am completely stuck and confused why it works with one View to Controller but not the other one.
The one that works:
public class HomeController : Controller
{
// GET: Home
IAuthenticationManager Authentication
{
get { return HttpContext.GetOwinContext().Authentication; }
}
public ActionResult Index()
{
return View();
}
[POST("login")]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel input)
{
if (ModelState.IsValid)
{
if (input.HasValidUsernameAndPassword())
{
var identity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Name, input.Username),
},
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name, ClaimTypes.Role);
// if you want roles, just add as many as you want here (for loop maybe?)
if (input.isAdministrator)
{
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
}
else
{
identity.AddClaim(new Claim(ClaimTypes.Role, "User"));
}
// tell OWIN the identity provider, optional
// identity.AddClaim(new Claim(IdentityProvider, "Simplest Auth"));
Authentication.SignIn(new AuthenticationProperties
{
IsPersistent = input.RememberMe
}, identity);
return RedirectToAction("password", "authentication");
}
}
return View("show", input);
}
[GET("logout")]
public ActionResult Logout()
{
Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("login");
}
}
With this CSHTML code:
#model AgridyneAllIn1MVC.Models.Authentication.LoginModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Details</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.Username, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Username)
#Html.ValidationMessageFor(model => model.Username)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Password, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.RememberMe, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.RememberMe)
#Html.ValidationMessageFor(model => model.RememberMe)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Login" class="btn btn-default" />
</div>
</div>
</div>
}
The CSHTML that doesn't work is:
#using Microsoft.AspNet.Identity
#model AgridyneAllIn1MVC.Models.Authentication.LoginModel
#if(User.Identity.IsAuthenticated)
{
using (Html.BeginForm("resetpassword", "authentication", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Reset your password.</h4>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Username, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Username, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Reset" />
</div>
</div>
}
}
The Controller that goes with it is:
public class AuthenticationController : Controller
{
[Authorize]
public ActionResult Password()
{
return View("ResetPassword");
}
[Authorize]
public ActionResult Register()
{
return View("Register");
}
[Authorize]
[POST("resetpassword")]
public ActionResult ResetPassword(LoginModel input)
{
if (ModelState.IsValid)
{
if (User.Identity.IsAuthenticated)
{
if(input.SuccessfullyUpdatedPassword())
{
return View("Password");
}
}
}
return View("Password");
}
[Authorize]
[POST("registernewuser")]
public ActionResult RegisterNewUser(LoginModel input)
{
if (ModelState.IsValid)
{
if (User.Identity.IsAuthenticated)
{
if (input.SuccessfullyUpdatedPassword())
{
return View();
}
}
}
return View();
}
}
Every time I try to get to either of those pages I get
The resource cannot be found. HTTP 404. Requested URL: /resetpassword.
Am I missing something that is required in the HTML.BeginForm() that would make it go to the correct ActionEvent to fire off my code and update the view? This is my first MVC that I am building from scratch and designing the views from scratch. Does MVCs have a problem with having multiple views to one controller? What gets me is that the MVC that works doesn't have anything filled in the HTML.BeginForm(), I tried it without having anything in HTML.BeginForm() and I get the same result. I tried with changing the [GET("resetpassword")] to [GET("")] and it doesn't work either. I have another view that is called register and it has the same problem with the AuthenticationController. So can someone please tell me what is wrong and explain why so I can fully understand how MVC wants things to be done so I get it right for the other 50 plus views that I still have to build yet.
Your Form use FormMethod.Post but there aren't exist POST resetpassword method in your Controller
Change the [GET("resetpassword")] to POST because the form from cshtml file use the post method:
using (Html.BeginForm("resetpassword", "authentication", FormMethod.Post,....
I am having trouble getting my view to call the post method in my MVC Controller. When I click on the submit button, it does not call the Create method. I add a breakpoint, but it never gets to the code. I am assuming some error, but not sure how to see the error message.
Here is the View:
#model PersonViewModel
#{
ViewBag.Title = "Register";
}
#using (Html.BeginForm(PeopleControllerAction.Create, ControllerName.People, FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal row">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
</div>
<div class="row panel radius">
<div class="medium-2 columns">
<h3>Contact Information</h3>
</div>
<div class="medium-10 columns">
<div class="row">
<div class="medium-6 columns">
<div class="form-group">
#Html.LabelFor(model => model.FirstName, htmlAttributes: new { #class = "control-label" })
<div>
#Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
</div>
</div>
</div>
</div>
// more fields removed for brevity
<div class="row">
<div class="form-group">
<div>
<input type="submit" value="Submit" class="button" />
</div>
</div>
</div>
}
Here is the controller:
public class PeopleController : Controller
{
private IPersonService context { get; set; }
public PeopleController(IPersonService context)
{
this.context = context;
}
// POST: People/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "FirstName,LastName,Age,Email,Phone,City,State,HopeToReach,Story,Goal,Image")] PersonViewModel person)
{
if (ModelState.IsValid)
{
try
{
person.ImagePath = ImageUploader.UploadImage(person.Image);
}
catch (ArgumentException e)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, e.Message);
}
var id = await context.AddAsync(person);
return RedirectToAction(PeopleControllerAction.Confirmation);
}
return View(person);
}
}
This was resolved. The action in question did not have a route. I am using attribute routing.