Redirecting to a response view with a model does not keep model properties - asp.net-mvc-4

I have a form view that submits form data to the post action on a controler and then redirects to another view that uses logic to display either a success or failure, but the new view just shows blank values for model properties. Here is the post action:
[HttpPost]
public ActionResult ContactUs(TTT.Models.ContactUsModel model)
{
logger.Info(model.URL + "Contact Us Form submitted");
var userkey = model.ValidationKey;
var sessionkey = Session["ContactUsKey"];
var lastsubmission = Session["ContactUsTime"];
model.Response = "success";
//first check if honeypot was populated via a bot and if so send it to the success page without doing anything
if (model.WorkAddress != "")
{
logger.Info("honeypot triggered");
return View("ContactUsResponse", model);
}
I'll leave out the remainder of the controler, but
And here is the view it's redirecting to:
#using TTT.Models
#using Sitecore.Mvc
#model ContactUsModel
<h1>#Model.Title</h1>
<div>#Model.Body</div>
<div>
#if (#Model.Response == "fail")
{
#Model.Failure;
} else
{
#Model.Success;
}
</div>

Instead of returning a new view, call RedirectToAction and return new view from that controller.
[HttpPost]
public ActionResult ContactUs(TTT.Models.ContactUsModel model)
{
//--- Code omitted for brevity
if (model.WorkAddress != "")
{
logger.Info("honeypot triggered");
return RedirectToAction("ContactUsResponse", new { response = model });
}
}
public ActionResult ContactUsResponse(TTT.Models.ContactUsModel response)
{
return View(model)
}

Related

How to disable direct access to a view from url?

I have two views in my asp net core application. The first view is called customer and the second view is called payment. I want to disable that users can get direct acces by typing the url "https://mywebsite/Payment" in the browser.
I want the users to be redirected to view which is called customer If users are trying to get direct access to view called payment.
How can I do that. I don't have any idea.
You could create a filter as below :
public class NoDirectAccessAttribute:ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var canAccess = false;
//check the refer
var referer = context.HttpContext.Request.Headers["Referer"].ToString();
if(!string.IsNullOrEmpty(referer))
{
var request = context.HttpContext.Request;
var rUri = new System.UriBuilder(referer).Uri;
if(request.Host.Host==rUri.Host && request.Host.Port==rUri.Port && request.Scheme==rUri.Scheme)
{
canAccess = true;
}
}
// ... check other requirements
if (!canAccess)
{
context.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Home", action = "Index", area = "" }));
}
}
}
Then you can apply NoDirectAccess Attribute to specific Action
[NoDirectAccess]
public IActionResult Privacy()
{
return View();
}

Usage of UseStatusCodePagesWithReExecute with a message not working as expected

I'm using UseStatusCodePagesWithReExecute in my .NET Core 2.1 web app as follows
app.UseStatusCodePagesWithReExecute("/Error/{0}");
and in my Controller I point to 1 of 2 views, a 404.cshtml view and a generic error.cshtml view
public class ErrorController : Controller
{
[HttpGet("[controller]/{statusCode:int}")]
public IActionResult Error(int? statusCode = null)
{
if (statusCode.HasValue)
{
if (statusCode == (int)HttpStatusCode.NotFound)
{
return View(statusCode.ToString());
}
}
return View();
}
}
Now in my page controller I can do the following and it works as expected. It will show error.cshtml
public IActionResult SomePage()
{
return BadRequest();
}
Now if I change the above to the following, my ErrorController does get hit but by the time it does a blank view showing just "Some details" has been loaded in the browser.
public IActionResult SomePage()
{
return BadRequest("Some details");
}
Any ideas why? I want it to load error.cshtml
As #Kirk Larkin said , UseStatusCodePagesWithReExecute middleware won't work and it will only handle the status code .
You can use Result filters to write your custom logic to filter that and return a ViewResult :
public class StatusCodeResultFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// retrieve a typed controller, so we can reuse its data
if (context.Controller is Controller controller)
{
// intercept the NotFoundObjectResult
if (context.Result is BadRequestObjectResult badRequestObject)
{
// set the model, or other view data
controller.ViewData.Model = badRequestObject.Value;
// replace the result by a view result
context.Result = new ViewResult()
{
StatusCode = 400,
ViewName = "Views/Error/status400.cshtml",
ViewData = controller.ViewData,
TempData = controller.TempData,
};
}
}
await next();
}
}
Register the filter :
services.AddMvc(config =>
{
config.Filters.Add(new StatusCodeResultFilter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
In your view , you can directly get the detail message by :
#Model
Reference : https://stackoverflow.com/a/51800917/5751404

MVC Form action method gets overwritten

I have the following razor form:
#model
#using (Html.BeginForm("ResetPassword", "User", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.Hidden("guid", ViewData["guid"]) ....ect it contains a model and 1 hidden field
When i hit the page i must pass a guid i do this the following way:
User/ResetPassword/8C5F38CC-C8DB-46B4-80F5-169699D8A583
I hit the action controller as expected:
public ActionResult ResetPassword(string id)
{
ViewBag.Title = #DDHelper.GetContent("user_password_reset_new") + " " +
#DDHelper.GetContent("slogan") + " " + #DDHelper.GetMeta("sitename");
if (id != null)
{
Guid pwID = new Guid();
if (Guid.TryParse(id, out pwID))
{
if (UserManager.GetResetPasswordUser(pwID) != null)
{
ViewData["guid"] = id;
return View(new Models.User());
}
}
}
return View();
}
Now when i look at the html razor produced i see:
<form action="/User/ResetPassword/8C5F38CC-C8DB-46B4-80F5-169699D8A583" class="form-horizontal" method="post" role="form">
When i post the form i want to hit the action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ResetPassword(Models.User pwUser)
{
string guid = Request["guid"];
string password = pwUser.Password;
Guid pwID = new Guid();
if (Guid.TryParse(guid, out pwID))
{
UserManager.ResetUserPassword(password,pwID);
return RedirectToAction("LogOn");
}
return View(guid);
}
Now when I post the form I hit the cshtml again and i am not hitting my action because the action
/User/ResetPassword/8C5F38CC-C8DB-46B4-80F5-169699D8A583
does not exsist and everytime someone hits this page the guid is different.
How can i tell the html.beginform to not write parameters in the action name? and why is razor behaving like this?

Redirect to action with parameters always null in mvc

When I tried redirect to action, the parameter is always null when I received ? I don't know why this happening like these.
ActionResult action1() {
if(ModelState.IsValid) {
// Here user object with updated data
redirectToAction("action2", new{ user = user });
}
return view(Model);
}
ActionResult action2(User user) {
// user object here always null when control comes to action 2
return view(user);
}
And with this I've another doubt. when I accessed action with route, i can get values only by RouteData.Values["Id"]. the values routed doesn't send to parameter.
<a href="#Url.RouteUrl("RouteToAction", new { Id = "454" }> </a>
Here Am I miss any configure ? or anything I miss.
ActionResult tempAction(Id) {
// Here Id always null or empty..
// I can get data only by RouteData.Values["Id"]
}
You cannot pass complex objects in an url like that. You will have to send its constituent parts:
public ActionResult Action1()
{
if (ModelState.IsValid)
{
// Here user object with updated data
return RedirectToAction("action2", new {
id = user.Id,
firstName = user.FirstName,
lastName = user.LastName,
...
});
}
return view(Model);
}
Also notice that I have added the return RedirectToAction instead of only calling RedirectToAction as shown in your code.
But a much better approach is to send only the id of the user:
public ActionResult Action1()
{
if (ModelState.IsValid)
{
// Here user object with updated data
return RedirectToAction("action2", new {
id = user.Id,
});
}
return view(Model);
}
and in your target action use this id to retrieve the user from wherever this user is stored (could be database or something):
public ActionResult Action2(int id)
{
User user = GetUserFromSomeWhere(id);
return view(user);
}
Some alternative approaches (but one I don't recommend or use) is to persist the object in TempData:
public ActionResult Action1()
{
if(ModelState.IsValid)
{
TempData["user"] = user;
// Here user object with updated data
return RedirectToAction("action2");
}
return view(Model);
}
and in your target action:
public ActionResult Action2()
{
User user = (User)TempData["user"];
return View(user);
}

ASP.NET MVC 4 DropDownListFor error: Null Values

I am a beginner programmer and having trouble with the #Html.DropDownListFor helper...
I am using a General Repository and Unit of Work pattern based off of the tutorial here:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
Here is my code for the Repository:
public class GenericRepository<TEntity> where TEntity : class
{
internal UsersContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(UsersContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
// Delete methods not shown
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
Here is my code for my UnitOfWork class:
public class UnitOfWork : IDisposable
{
private UsersContext context = new UsersContext();
private GenericRepository<UserProfile> userProfileRepository;
private GenericRepository<Lead> leadRepository;
private GenericRepository<UnitedStatesState> unitedStatesStateRepository;
public GenericRepository<UserProfile> UserProfileRepository
{
get
{
if (this.userProfileRepository == null)
{
this.userProfileRepository = new GenericRepository<UserProfile(context);
}
return userProfileRepository;
}
}
public GenericRepository<Lead> LeadRepository
{
get
{
if (this.leadRepository == null)
{
this.leadRepository = new GenericRepository<Lead>(context);
}
return leadRepository;
}
}
public GenericRepository<UnitedStatesState> UnitedStatesStateRepository
{
get
{
if (this.unitedStatesStateRepository == null)
{
this.unitedStatesStateRepository = new GenericRepository<UnitedStatesState>(context);
}
return unitedStatesStateRepository;
}
}
I am trying to use strongly typed views and models in order to pass the selectlist data to the view without using ViewData/ViewBag. From what I understand, the best practice is to do something similar to what I saw here:
validate a dropdownlist in asp.net mvc
I tried following that as closely as possible and this is what I came up with
My View Model looks like this:
public class Lead
{
public int LeadID { get; set; }
public int CompanyID { get; set; }
[Required(ErrorMessage = "Please enter state")]
[Display(Name = "State")]
[MaxLength(2)]
public string State { get; set; }
[Display(Name = "Assigned To")]
public string AssignedTo { get; set; }
[Timestamp]
public Byte[] Timestamp { get; set; }
public virtual Company Company { get; set; }
// IEnumerables for Dropdown Lists passed to views
public IEnumerable<UnitedStatesState> UnitedStatesStates { get; set; }
public IEnumerable<UserProfile> UserProfiles { get; set; }
// Objects passed to views
public Lead lead { get; set; }
}
These IEnumerables for my dropdown lists are then populated in my controller from my database through my repository. The odd part is that I am using these dropdown lists in two different views, Create and Edit. When I use the dropdown lists in the Create view they work perfectly both on the GET and POST ActionResults. When I try and use the same dropdown lists for my Edit view they work for the GET ActionResult (the view loads and the dropdowns work) but when I try to POST them to my Edit ActionResult I get the following error:
{"Value cannot be null.\r\nParameter name: items"} // This is the error as shown in Visual Studio 2012
System.ArgumentNullException: Value cannot be null.
Parameter name: items // This is the error shown in Google Chrome
Below is my Lead Controller with the Edit and Create ActionResults:
public class LeadController : Controller
{
// create instance of Repository Unit of Work
private UnitOfWork unitOfWork = new UnitOfWork();
public ActionResult Create()
{
// Get the current users profile
UserProfile userProfile = UserProfile.GetCurrentUserProfile();
// Creates Dropdown Lists to pass to view
var model = new Lead
{
UnitedStatesStates = unitOfWork.UnitedStatesStateRepository.Get(u => u.StateAbbreviation != null),
UserProfiles = unitOfWork.UserProfileRepository.Get(u => u.CompanyID == userProfile.CompanyID)
};
// Return View
return View(model);
}
[HttpPost]
public ActionResult Create(Lead model)
{
try
{
if (ModelState.IsValid)
{
// Call the current users profile
UserProfile userProfile = UserProfile.GetCurrentUserProfile();
// Create a new lead and apply all attirbutes that were entered
Lead lead = new Lead();
lead.CompanyID = userProfile.CompanyID;
lead.State = model.State;
lead.AssignedTo = model.AssignedTo;
// Add the lead and save the changes. Redirect to Lead Index.
unitOfWork.LeadRepository.Insert(lead);
unitOfWork.Save();
return RedirectToAction("Index");
}
}
catch (DataException)
{
ModelState.AddModelError("", "Unable to save changes. Try again and if the problem persists, see your system administrator.");
}
// Return view if ModelState is not valid
return View();
}
public ActionResult Edit(int id = 0)
{
// Get Users Profile
UserProfile userProfile = UserProfile.GetCurrentUserProfile();
// Check to see if Lead Exists
if (unitOfWork.LeadRepository.GetByID(id) == null)
{
return HttpNotFound();
}
// Creates Dropdown Lists and Gets current lead values to pass to view
var model = new Lead
{
lead = unitOfWork.LeadRepository.GetByID(id),
UnitedStatesStates = unitOfWork.UnitedStatesStateRepository.Get(u => u.StateAbbreviation != null),
UserProfiles = unitOfWork.UserProfileRepository.Get(u => u.CompanyID == userProfile.CompanyID)
};
return View(model);
}
[HttpPost]
public ActionResult Edit(Lead lead)
{
try
{
// Update lead if model state is valid
if (ModelState.IsValid)
{
unitOfWork.LeadRepository.Update(lead);
unitOfWork.Save();
return RedirectToAction("Index");
}
}
// Catch any concurrency exceptions
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var databaseValues = (Lead)entry.GetDatabaseValues().ToObject();
var clientValues = (Lead)entry.Entity;
if (databaseValues.State != clientValues.State)
ModelState.AddModelError("State", "Current value: "
+ databaseValues.State);
if (databaseValues.AssignedTo != clientValues.AssignedTo )
ModelState.AddModelError("Assigned To ", "Current value: "
+ databaseValues.AssignedTo );
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
lead.Timestamp = databaseValues.Timestamp;
}
catch (DataException)
{
//Log the error (add a variable name after Exception)
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
}
// Return View if Model State is not valid
return View(lead);
}
The POST Edit ActionResult includes code to catch concurrencies which I created following the tutorial shown here:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
Below is my view for Create (this works perfectly):
#model SolarToolbase.Models.Lead
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<div>
<div>
#Html.LabelFor(model => model.State)
#Html.DropDownListFor(model => model.State, new SelectList(Model.UnitedStatesStates, "StateAbbreviation", "UnitedStatesStateName"),"Choose State")<br />
#Html.ValidationMessageFor(model => model.State)
</div>
<div>
#Html.LabelFor(model => model.AssignedTo)
#Html.DropDownListFor(model => model.AssignedTo, new SelectList(Model.UserProfiles, "FullName", "FullName"),"Choose User")<br />
#Html.ValidationMessageFor(model => model.AssignedTo)
</div>
<p>
<input type="submit" value="Create" />
</p>
</div>
}
Below is my view for Edit(this throws the aforementioned errors when I hit the submit button. I inserted a comment below to show the line that the error is being thrown from):
#model SolarToolbase.Models.Lead
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.lead.LeadID)
#Html.HiddenFor(model => model.lead.Timestamp)
<div>
<div>
#Html.LabelFor(model => model.lead.State)
#Html.DropDownListFor(model => model.lead.State, new SelectList(Model.UnitedStatesStates, "StateAbbreviation", "UnitedStatesStateName"))<br /> // Error thrown from this line
#Html.ValidationMessageFor(model => model.lead.State)
</div>
<div>
#Html.LabelFor(model => model.lead.AssignedTo)
#Html.DropDownListFor(model => model.lead.AssignedTo, new SelectList(Model.UserProfiles, "FullName", "FullName"))<br />
#Html.ValidationMessageFor(model => model.lead.AssignedTo)
</div>
<p>
<input type="submit" value="Save" />
</p>
</div>
}
I apologize in advance for posting so much code, I just honestly don't know where this error is coming from and I've beat my head against the wall trying to figure it out for about 4 hours now. Free virtual high fives and good karma for anyone that can help.
Thanks!
In the case of a POST to both the Create and Edit actions, when there's an error or the ModelState is invalid, you catch any exceptions and return the default View with the constructed Lead view model, created and populated by the model binder.
In the Edit POST action though, if there is an error condition, you return the lead object to the View as its Model. Note that the UnitedStatesStates and the UserProfiles properties are not repopulated upon a POST. You populate them in the GET actions, but you have to do that in the POST actions too. You need to be careful that whatever model you are sending to the view is in proper shape, and it has all expected members populated.
Also notice your view model is of type Lead which has a property called lead. That's a code smell there; I wouldn't have a view model class having a reference to an instance of its own class. It's causing confusion for you already. I'd have Lead be LeadViewModel to be explicit and just have it hold all the properties and values it needs when going to and from the views, with no lead property.
In your Edit view, you're referencing the model's properties as model.lead.State for example, but in the Create view you're referencing the parent-level properties, as in model.State. But in the Edit view, when it comes to the SelectListItems you're using Model.UnitedStatesStates instead of Model.lead.UnitedStatesStates. As I said I'd do away with this pattern and do what the Create view does now, not having a child lead property at all. Just do model.State for example, for all properties and in both views.
So make sure your collection properties are populated whenever you pass the model to the view, as in
[HttpPost]
public ActionResult Edit(Lead lead)
{
try
{
// Update lead if model state is valid
if (ModelState.IsValid)
{
unitOfWork.LeadRepository.Update(lead);
unitOfWork.Save();
return RedirectToAction("Index");
}
}
// Catch any concurrency exceptions
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var databaseValues = (Lead)entry.GetDatabaseValues().ToObject();
var clientValues = (Lead)entry.Entity;
if (databaseValues.State != clientValues.State)
ModelState.AddModelError("State", "Current value: "
+ databaseValues.State);
if (databaseValues.AssignedTo != clientValues.AssignedTo )
ModelState.AddModelError("Assigned To ", "Current value: "
+ databaseValues.AssignedTo );
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
lead.Timestamp = databaseValues.Timestamp;
}
catch (DataException)
{
//Log the error (add a variable name after Exception)
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
}
// Return View if Model State is not valid
/////////// CHANGES HERE
lead.UnitedStatesStates = unitOfWork.UnitedStatesStateRepository.Get(u => u.StateAbbreviation != null),
lead.UserProfiles = unitOfWork.UserProfileRepository.Get(u => u.CompanyID == userProfile.CompanyID)
return View(lead); // pass the model to the view for Create and Edit POST actions when there's an error
}
Do that in both POST actions. If there's an error, the view will be instantiated by the action with a populated model. Also change the Edit view to work just like the Create view, and not use the Lead view model's lead property. Presumably that will take care of any null reference exceptions in the views.