I have a MVC4 controller that calls its view multiple times, each time with a different set of ViewBags. The view renders the contents of a Form based on the absence or presence of those ViewBags via something like this:
#using (Html.BeginForm())
{
#if (ViewBag.Foo1 != null)
{
#Html.DropDownList("Bar1",....
}
#if (ViewBag.Foo2 != null)
{
#Html.DropDownList("Bar2",....
}
<input name="ActionButton" type="submit" value="Ok"" />
}
Each time the user clicks the submit button, the controller checks to see what is in the collection and makes a new set of ViewBags before calling the view again, sort of like this:
public ActionResult Create()
{
ViewBag.Foo1 = "blawblaw";
return View();
}
[HttpPost]
public ActionResult Create(FormCollection collection)
{
if (collection["Bar1"] != null)
{
string FirstPass = collection["Bar1"];
ViewBag.Foo2 = "blawblaw";
}
if (collection["Bar2"] != null)
{
string SecondPass = collection["Bar2"];
ViewBag.Foo3 = "blawblaw";
}
return View();
}
What I need to do now is somehow have each pass thu the controller 'remember' something about its previous passes. That is, in my example, the second pass thru the controller (the one where collection["Bar2"] is true), the value of FirstPass is null.
How can I do that?
In that case have a look at best practices for implementing a wizard in MVC. Some good suggestions here. Personally I would still consider using separate and distinct urls. Also, If you have db access in your solution you can still store temporary data before updating the main model. Think about what you want to happen if the user doesn't complete the whole journey the first time round...
Related
The subject might not be clear since I couldn't find a better way to express it.
I am developing a web application using ASP.NET Core 6.0 with Razor Pages. Our previous application was an SPA using Ext JS where any call to server was returning only data and where I was also able to make any kind of call (GET/POST) to get the data.
For example, in the above picture from my old application, I make an ajax call with POST to get the list of periods when I open this page. I make a POST because I am sending the period type in my request payload. Sure I can pass these parameters in a GET request, however my other views have many criteria, so passing these criteria in the query string is not what I want. So, I decided to make it a standard to make my calls with POST method if there are any criteria payload, make GET request only when fething an entity with a simple key parameter (like Id) or GET any list that doesn't have any criteria.
Now, I am quite confused how to do same thing in my new ASP.NET Core Razor Pages web application. Normally, the menu items navigate to the page using link as below, which makes a GET request:
<a asp-area="System" asp-page="/ProfessionList">#AppLocalizer["Profession List"]</a>
<a asp-area="System" asp-page="/PeriodList">#AppLocalizer["Profession List"]</a>
In order to make a POST request, I replaced the menu item for period list as following which makes a POST request with a default periodType payload:
<a asp-area="System" asp-page="/ProfessionList">#AppLocalizer["Profession List"]</a>
<form asp-area="System" asp-page="/PeriodList" method="post">
<input type="hidden" name="periodType" value="1" hidden />
<button type="submit" >#AppLocalizer["Period List"]</button>
</form>
And the corresponding PeriodType.cshtml.cs file is as following:
[Authorize]
public class PeriodListModel: BaseEntityListPageModel<List<JsonPeriodEx>> {
public PeriodListModel(ILogger<BaseEntityListPageModel<List<JsonPeriodEx>>> logger, WebApi webApi) : base(logger, webApi) {
}
public IActionResult OnGet() {
PageData = JsonConvert.DeserializeObject<List<JsonPeriodEx>>(TempData["PageData"].ToString());
return Page();
}
public async Task<IActionResult> OnPostAsync(int periodType) {
var jsonResult = await _WebApi.DoPostAsync<List<JsonPeriodEx>>("/PeriodEx/GetList", new[] { new { Property = "periodType", Value = periodType } });
if (jsonResult.IsLoggedOut)
return RedirectToPage("/Login", new { area = "Account" });
if (jsonResult.Success) {
PageData = jsonResult.Data;
TempData["PageData"] = JsonConvert.SerializeObject(PageData);
return RedirectToPage("/PeriodList");
} else {
return RedirectToPage("/Error");
}
}
}
OnPostAsync successfully binds to the posted periodType parameter and gets the list of periods. Now, at the end of a successful call I want to follow the Post/Redirect/Get pattern and redirect to OnGet with the data from OnPostAsync, which is stored in TempData.
Now, according to the above scenario, is my approach, explained above, correct or should I implement it differently?
Thanks in advance
For these cases I would prefer TempData. Much easier and less code.
public async Task OnGet()
{
TempData["myParamToPass"] = 999;
...
}
public async Task OnPostReadData()
{
if (TempData.ContainsKey("myParamToPass"))
{
var myParamToPassValue = TempData.Peek("myParamToPass") as int?;
...
}
...
}
I'm trying to display a specific value from a column on the page and I'm using ASP.NET Core MVC with Entity FrameWork Core to do so. The problem which I'm facing that it does not return the the column value, it rather returns some method.
Here's the controller
public IActionResult newTicket()
{
string getUsername = HttpContext.Session.GetString("username");
ViewBag.username = _context.users.Where(o => o.username == getUsername).ToList();
return View();
}
So basically what this action should be doing that it should get username from the session first. Then, it should run a query in the database such that if there is a similar username in the database then pass the value of username to the view by using viewbag.
The view
<input class="form-control w-50" asp-for="cName" required="required" value="#ViewBag.username"/>
This is how the output looks, I don't want it to look like that, I want it to display username.
https://i.stack.imgur.com/n6dXo.png
If I had to explain what I mean in this question through another language (PHP MySQL) is that.
$query = mysqli_query($connection_variable,"SELECT * from complaints
WHERE username = '$username'");
while ($row = mysqli_fetch_assoc($query))
{
echo $row['username'];
}
This is what I mean, in case I failed to explain you can ask for details.
Your ViewBag use List, not user data.
Use this.
public IActionResult newTicket()
{
string getUsername = HttpContext.Session.GetString("username");
ViewBag.username = _context.users.Where(o => o.username == getUsername).FirstOrDefault()?.username;
return View();
}
What you can do in view something like this:
#foreach(User usr in ViewBag.username)
{
<b>#usr.username</b>
}
But now what you are doing is just calling the default ToString() method of collection. That's why you are getting that result. You should be calling the property of an element in that collection.
Or you can try this:
ViewBag.username = _context.users.FirstOrDefault(o => o.username == getUsername);
I have two controller actions, where one is used to return the view in a standard way and another returns a large chunk of the same view for use in, for example, a javascript modal somewhere else in my application. My question is what would be the best practise way to do this, or if the way I have done it is ok. Maybe I should move the duplicated code out to a helper method?
(Note the Create View has the _Create partial inside it)
Right now I have:
public ActionResult Create(int someParamater)
{
//Lots of code
return View(model);
}
public PartialViewResult GetCreatePartial(int someParameter)
{
//All of the same code as in Create
return PartialView("_Create", model);
}
You can check on some condition and return PartialView or View on its basis, instead of creating a separate action:
public ActionResult Create(int someParamater)
{
if(Request.IsAjaxRequest()) // check here if ajax call return partial
return PartialView("_Create", model);
else
return View(model); // otherwise return Full View
}
If indeed your GetCreatePartial method can be called standalone, and the someParameter argument is available in the View as well, you can call it within parent view using Html.Action().
For example (Create.cshtml):
<div>
<span>some parent view stuff</span>
</div>
<div class="partial-wrapper">
#Html.Action("GetCreatePartial", new { someParameter = Model.someParameter })
</div>
See Html.Action
I am storing image in Database successfully.
I want to display this image in Edit form to modify and save changes. But I'm just able to display image in Edit form my code for modifying and save images in data base is not working it's inserting null values in Image field when the record is modified and saved or if I modify all other fields in Edit view excepting image field.
Would someone please tell me what mistake I'm doing? Here is my controller action:
public ActionResult Edit(student st) {
if (ModelState.IsValid) {
var imgFile = Request.Files["imgFile"];
if (imgFile != null && imgFile.ContentLength > 0) {
var fileName = Path.GetFileName(imgFile.FileName);
var path = Path.Combine(Server.MapPath("~/Content/stImgs"), fileName);
imgFile.SaveAs(path);
st.Img = fileName;
}
}
try {
db.Entry(st).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("student");
} catch {
return View(st);
}
}
Here is view:
<img src="/Content/Imgs/#Model.Img">
</div>
<label for="file">Image:</label>
<input type="file" name="file" id="file" />
#Html.ValidationMessageFor(item => item.file)
try adding a HttpPostedFileBase parameter in your action method:
public ActionResult Edit(student st, HttpPostedFileBase file)
{
if (ModelState.IsValid) {
if (file != null && file.ContentLength > 0) {
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/Content/stImgs"), fileName);
file.SaveAs(path);
st.Img = fileName;
}
}
try {
db.Entry(st).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("student");
} catch {
return View(st);
}
}
Make sure the parameter's name is the same as the file's name in the form!
Also, make sure your Form is set to enctype multipart/form-data:
Html.BeginForm(
action, controller, FormMethod.Post, new { enctype="multipart/form-data"})
Microsoft change how update action method work.
please read section "Update the Edit HttpPost Method " from the following link.
These changes implement a security best practice to prevent overposting, The scaffolder generated a Bind attribute and added the entity created by the model binder to the entity set with a Modified flag. That code is no longer recommended because the Bind attribute clears out any pre-existing data in fields not listed in the Include parameter. In the future, the MVC controller scaffolder will be updated so that it doesn't generate Bind attributes for Edit methods.
The new code reads the existing entity and calls TryUpdateModel to update fields from user input in the posted form data. The Entity Framework's automatic change tracking sets the Modified flag on the entity. When the SaveChanges method is called, the Modified flag causes the Entity Framework to create SQL statements to update the database row. Concurrency conflicts are ignored, and all columns of the database row are updated, including those that the user didn't change. (A later tutorial shows how to handle concurrency conflicts, and if you only want individual fields to be updated in the database, you can set the entity to Unchanged and set individual fields to Modified.)
I am building my first MVC4 website and I would like to show success message when page successfully submitted. I have achieved by using ModelState.AddModelError(("", "Data successfully saved."); but it is showing in the red color. I want to apply different css at runtime based on some conditions.
Thanks.
I recommend using TempData instead of changing validationsummary and #von described it very well. Use bootstrap. You could do something like this:
Controller
[HttpPost]
public ActionResult ManageUsers(UserViewModel model)
{
if (ModelState.IsValid)
{
User obj = new User();
obj.UserName = model.Email;
obj.FirstName = model.FirstName;
obj.LastName = model.LastName;
obj.Email = model.Email;
obj.Phone = model.Phone;
obj.Notes = model.Notes;
obj.Authorized = model.Authorized;
UserRepository.SaveUser(obj);
TempData["Success"] = "Yes";
}
return View(model);
}
View
#Html.ValidationSummary()
#if (TempData["Success"] != null)
{
<div class="alert alert-success">
×
<strong>Success!</strong> The User account was created.
</div>
}
Normally when the result of an action method is successful a redirect happens, maybe that's what you want, especially if your result is not a json result. But if you are returning the same view after your post then you are doing it incorrectly. If the ModelState is valid on a post, that is if the validation passed (e.g. required fields are supplied), and you add an error message by doing ModelState.AddModelError(("", "Data successfully saved.") then you are making the ModelState go into an invalid state. That is the reason why you have the red color.
Now assuming you really want to return the same view then I suppose you have something like:
[HttpPost]
public ActionResult YourActionMethod(YourModel model)
{
// some code goes here
ModelState.AddModelError(("", "Data successfully saved.")
return View(", model);
}
What you should have instead is something like this:
[HttpPost]
public ActionResult YourActionMethod(YourModel model)
{
// some code goes here
ViewBag.SuccessMessage = "Data successfully saved.";
return View(", model);
}
Then on your view something like:
#Html.ValidationSummary(true)
if (!string.IsNullOrWhiteSpace(ViewBag.SuccessMessage)) {
<div class="success-summary">
<p>#ViewBag.SuccessMessage</p>
</div>
}
Note that you don't need an additional # before the if, that code assumes it's inside a form tag, using #using. And then for the css:
.success-summary {
color: #3366FF;
}
You can actually use either ViewData or ViewBag. To know more about the difference of the two you can visit this SO page.
UPDATE:
[HttpPost]
public ActionResult YourActionMethod(YourModel model)
{
//
If (ModelState.IsValid) {
#ViewBag.IsModelValid = true;
ModelState.AddModelError("", "Data successfully saved.");
return View(model);
}
ViewBag.SuccessMessage = "Data successfully saved.";
return View(", model);
}
Your view:
#Html.ValidationSummary(false, "", new { #class= (ViewBag.IsModelValid!=null && ViewBag.IsModelValid) ? "success-summary" : "" })
Von, I too appreciate your answer, but I agree with MaxPayne that you didn't quite provide an answer for the question, more of a work around IMO.
I too am looking at a way to style the ValidationSummary without the extra baggage of using the ViewBag.
I do agree that you shouldn't return to the same view after a post unless there are errors, but I do believe there are times when one might want to change the ValidationSummary style dynamically without having to use the ViewBag.
So far this is my only lead http://westcountrydeveloper.wordpress.com/2011/07/06/mvc-validation-part-4-styling-the-validation-controls/
I suppose you could use some JQuery to change the element's css attributes based on the Validation response.
var valid = $("#formID").validate().element("#ElementID");
//or
var valid = $('#formID').validate();
// Then use $(".ElementClass").css({}); to change the the style