How to pass the model items between MVC Actions - c#

I want create a Master/Detail page that shows properties of the model as well items of properties of the same model that are collections. The page itself should only have one save button, that stores the values in a database. I also want to allow the the user to make changes to the collection properties, that are shown on the page without saving them into the database. The following code shows the setup for the picture collection, but I also want to do this for a "Child-table/grid" i.e. collection of "pocos". Is there a way to do this in MVC?
To my understanding, I would have to keep the instance of the object and pass it between the HTMLActions, as this instance holds all the changes.
Just some pointers in the right direction would be nice or, if the case, pointing out, that MVC should not be used for this...
The model:
public class MasterModel : ModelBase
{
public MasterModel()
{
}
private int id;
public int Id
{
get { return id; }
set { id = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private ListBase<PicModel> pics;
public ListBase<PicModel> Pics
{
get { return pics; }
set { pics = value; }
}
}
Controller:
public ActionResult Edit(int id)
{
if (id <= 0 )
{
return RedirectToAction("Index");
}
m = new MasterModel (id);
return View(m);
}
[HttpPost]
public ActionResult NewPic(int id, HttpPostedFileBase uploadFile)
{
PicModel p = new PicModel();
MemoryStream ms = new MemoryStream();
uploadFile.InputStream.CopyTo(ms);
b.Picture= ms.ToArray();
m.Pics.Add(b); //Here it fails, as the MasterModel m is a different one then when the ActionResult Edit is called
}
View:
#model app1.Models.MasterModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/index.js")" type="text/javascript"></script>
<script>
$("#PicForm").on("submit", function (e) {
e.preventDefault();
var form = $(this);
var formData = new FormData(form.get(0));
$.ajax({
url: form.attr("action"),
method: form.attr("method"),
data: formData,
processData: false,
contentType: false
})
});
</script>
<div class="col-md-4 col-lg-4">
#using (Html.BeginForm("NewPic", "MasterModel ", FormMethod.Post, new { id = "PicForm", enctype = "multipart/form-data" }))
{
#Html.HiddenFor(model => model.Id)
<div class="container-fluid">
#foreach (app1.Models.PicModel b in Model.Pics)
{
var base64 = Convert.ToBase64String(b.Picture);
var imgSrc = String.Format("data:image/gif;base64,{0}", base64);
<img src="#imgSrc" width="200" height="200" />
}
</div>
<div>
<input type="file" id="uploadFile" name="uploadFile" />
<input type="submit" value="uploadFile" class="submit" />
</div>
}
</div>
Update 06.01.2018:
What works in MVC5 is to use the sessionString. However, I've learned that this won't work in asp.net Core.
Set:
m = (MasterModel )System.Web.HttpContext.Current.Session["sessionString"];
Get:
System.Web.HttpContext.Current.Session["sessionString"] = m;

or, ..., that MVC should not be used for this...
Pure MVC won't cut it, and you're already on your way with the Ajax calls.
But you'll find that that gets more and more complicated.
The best route would be to study up on SPA, with for instance Angular.
What works in MVC5 is to use the Session[].
Yes, but that is server-side state manangment, problems with scale-out etc.
But usable, for ASP.NET Core you could use the MemoryCache, or step up to ReDis. You still have (can configure) a Session Id.
With a SPA you won't need the cache/session so much, just use it for optimization.

Try TempData to store your Data and access in next Request.

Related

How to pass an input string into a ViewComponent?

Using Core 3.1 and Razor Pages
I trying to undertake the simple task of passing a search string into a ViewComponent and invoke the results.
I have encountered two issue I cannot find help with:
How to pass the input search string to the view component?
How to invoke the view component when the search button is clicked?
_Layout Page
<input id="txt" type="text" />
<button type="submit">Search</button>
#await Component.InvokeAsync("Search", new { search = "" })
//Should equal input string
I am new to core so any nudges in the right direction would be appreciated.
View component is populated on server side and then return to your client for rendering, so you can't directly pass client side input value into view component . In your scenario , when clicking search button , you can use Ajax to call server side method to load the view component and pass the input value :
Index.cshtml
<input id="txt" type="text" />
<button onclick="loadcomponents()">Search</button>
<div id="viewcomponent"></div>
#section Scripts{
<script>
function loadcomponents() {
$.ajax({
url: '/?handler=Filter',
data: {
id: $("#txt").val()
}
})
.done(function (result) {
$("#viewcomponent").html(result);
});
}
</script>
}
Index.cshtml.cs
public IActionResult OnGetFilter(string id)
{
return ViewComponent("Users", new { id = id });
}
UsersViewComponent.cs
public class UsersViewComponent : ViewComponent
{
private IUserService _userService;
public UsersViewComponent(IUserService userService)
{
_userService = userService;
}
public async Task<IViewComponentResult> InvokeAsync(string id)
{
var users = await _userService.GetUsersAsync();
return View(users);
}
}
Edit: Oh, you edited the razor tag in after I posted my answer. Well, my answer is only valid for ASP.NET Core MVC.
I assume that your controller looks something like this:
[HttpGet]
public IActionResult Index()
{
var model = new IndexVM();
return View(model);
}
[HttpPost]
public IActionResult Index(IndexVM model)
{
// you can do something with the parameters from the models here, or some other stuff
return View(model);
}
Your ViewModel can look like this:
public class IndexVM
{
public string SearchTerm {get;set;}
}
Your View where you use your ViewComponent:
#model IndexVM
// <form tag ...
<input asp-for="SearchTerm" />
<button type="submit">Search</button>
#await Component.InvokeAsync(nameof(Search), Model)
ViewComponent:
public class Search : ViewComponent
{
public IViewComponentResult Invoke(IndexVM indexVM)
{
// Do something with indexVM.SearchTerm
}
}
View of ViewComponent:
#model IndexVM
// ...

Render whole page with PartialView (and data) on page load

Currently we have a page where you select some parameters and click on a button to load data and display it in a grid, but there is no functionality to display the data on page load (via url parameters) yet. I've added the necessary routing configurations and Action, but I'm having troubles to render the page, it only displays the PartialView without styles.
How can I get the whole page to render and not just the PartialView?
Below is my simplyfied code for the View and Controller.
Views/Planing/Index.cshtml
#model PlaningTool.Web.Models.PlaningViewModel
<div class="row">
<div>
#using (Ajax.BeginForm("GetDataRows",
"Planing",
new AjaxOptions
{
HttpMethod = "Get",
UpdateTargetId = "gridPlaceholder",
LoadingElementId = "loadingIndicator"
}))
{
<!-- some comboboxes to select project and year -->
<input type="submit" value="Load data" />
}
</div>
</div>
<div id="gridPlaceholder">
<div id="loadingIndicator" style="display: none;">
<img src="~/Content/images/loading-image.gif" />
</div>
</div>
Controllers/PlaningController.cs
public partial class PlaningController : Controller
{
public virtual ActionResult Index()
{
return View();
}
public virtual ActionResult Plan(long projectID, int year)
{
var viewModel = new PlaningViewModel
{
ProjectID = projectID,
Year = year
};
// return GetDataRows(viewModel);
return RedirectToAction("GetDataRows", viewModel);
}
[RestoreModelStateFromTempData(typeof(PartialViewResult))]
public virtual PartialViewResult GetDataRows(PlaningViewModel viewModel)
{
// Load data from database with viewModel.ProjectID
// and viewModel.Year as parameters
[...]
var vm = new PlaningViewModel
{
// Set ViewModel for loaded data
[...]
};
return PartialView("Shared/_PlaningViewModelRows", vm);
}
[...]
}
I finally found a solution. I'm pretty sure it's not the best way to do this but it works.
If the Model is already set I render the PartialView.
<div id="gridPlaceholder">
#{
if (Model != null)
{
Html.RenderPartial("Shared/_PDataViewModelRows", Model);
}
}
<div id="loadingIndicator" style="display: none;">
<img src="~/Content/kendo/Bootstrap/loading-image.gif"/>
</div>
</div>
And in my Controller I've changed to this, so my ViewModel gets loaded independently and I simply return the same view as I would for Index with the new ViewModel.
public virtual ActionResult Plan(long projectID, int year)
{
var viewModel = new PlaningViewModel
{
ProjectID = projectID,
Year = year
};
return View("Index", LoadViewModel(viewModel));
}
public PlaningViewModel LoadViewModel(PlaningViewModel viewModel)
{
// Load data from database with viewModel.ProjectID
// and viewModel.Year as parameters
[...]
var vm = new PlaningViewModel
{
// Set ViewModel for loaded data
[...]
};
return vm;
}

Counting how many times a button was clicked

I am wondering how can I count the number of times a button in my view was clicked using sessions and not using jQuery, just asp.net.
Here is my action method (empty) :
public ActionResult ClickCounter()
{
return View();
}
and my view :
#{
ViewBag.Title = "ClickCounter";
}
<h2>ClickCounter</h2>
#using (#Html.BeginForm())
{
<!-- form content here -->
#Session["num"] = 0;
<form method="post">
<fieldset>
<legend>Button clicks counter</legend>
<div>
<label for="Clciks">Clicks:</label>
<h2>#Session["num"]</h2>
</div>
<div>
<label> </label>
<input type="submit" value="Click!" class="submit" />
</div>
</fieldset>
</form>
}
Excuse me for the lame questions, but I am a complete novice and trying to understand how this stuff work. I tried googleing.
I just want to display the click count in the h2 in my view using sessions for the purpose.
Any tips will be appreciated.
If it is for simply increasing the clicked count on form submit, You can update your http post action method to read the session value if exist and increase and set it back. If not exist, initialize it.
const string sessionVariableName = "num";
public ActionResult ClickCounter()
{
if (Session[sessionVariableName] == null)
{
Session[sessionVariableName] = 0;
}
return View();
}
[HttpPost]
public ActionResult ClickCounter(string dummyParam)
{
if (Session[sessionVariableName] == null) // should not happen!
{
Session[sessionVariableName] = 0;
}
else
{
var n = (int)Session[sessionVariableName];
n++;
Session[sessionVariableName] = n;
}
return View();
}
Make sure that you are doing a GET form method on submit.
You also need to remove the (re) initialization in the view this line #Session["num"] = 0; as we are doing that in the action method. Also you should not have nested forms as it is invalid. Html.BeginForm helper will render the markup for the form tag. So remove the inner form tag you have.
You have tagged this question as asp.net-mvc, why not take advantage of the framework?
Model
class MyModel
{
public int ClickCount { get; set; }
}
View
#model MyModel
#{
ViewBag.Title = "ClickCounter";
}
<h2>#ViewBag.Title</h2>
<form method="post">
<!-- hidden input of the current click count -->
#Html.HiddenFor(m => m.ClickCount)
<fieldset>
<legend>Button clicks counter</legend>
<div>
#Html.LabelFor(m => m.ClickCount)
<h2>#Model.ClickCount</h2>
</div>
<div>
<button type="submit">Submit!</button>
</div>
</fieldset>
</form>
Controller
const string clickCountSessionKey = "clickCount";
[HttpGet]
public ActionResult ClickCounter()
{
// initialize the model
var model = new MyModel() { ClickCount = 0 };
var previousClickCount = Session[clickCountSessionKey];
if (previousClickCount != null)
{
model.ClickCount = (int)previousClickCount;
}
return View(model);
}
[HttpPost]
public ActionResult ClickCounter(MyModel model)
{
// increment the click count of the model
model.ClickCount++;
// track the click count in the session
Session[clickCountSessionKey] = model.ClickCount;
return View(model);
}

How do I get the selected item from drop down list and submit it to my Details view?

I have an mvc razor form. What i want is to submit the user's selection from Items dropdown list and navigate to Details view in order to access the chosen item's information.
Currently when i click the submit button i navigate succesfully to Details view but there is absolutely no data showing there.
Can you please help me?
View
#using (Html.BeginForm("Details", "Bookings", FormMethod.Post))
{
<fieldset>
<legend> Type/Item</legend>
<label for="Items">Item Types </label>
#Html.DropDownList("department", ViewBag.ItemTypesList as SelectList, "Select a Type", new { id = "ItemTypeID" })
<div id="ItemsDivId">
<label for="Items">Items </label>
<select id="ItemsID" name="Items"></select>
</div>
<p>
<input type ="submit" value="Submit" id="SubmitID" />
</p>
</fieldset>
}
<script type="text/javascript">
$('#ItemTypeID').on('change', function () {
$.ajax({
type: 'POST',
url: '#Url.Action("GetItemTypeForm")',
data: { itemTypeId: $('#ItemTypeID').val() },
success: function (results) {
var options = $('#ItemsID');
options.empty();
options.append($('<option />').val(null).text("- Select an Item -"));
$.each(results, function () {
options.append($('<option />').val(this.ItemsID).text(this.Value));
});
}
});
});
</script>
Controller:
public ActionResult Details(string id)
{
var item = db.Items.Find(id);
return View(item);
}
[HttpPost]
public JsonResult GetItemTypeForm(string itemTypeId)
{
//pseudo code
var data = from s in db.Items
where s.ItemType.ItemTypeName == itemTypeId
select new { Value = s.ItemName, ItemsID = s.ItemId };
return Json(data);
}
use Items in Details method if you want to get Items value
public ActionResult Details(int Items)
{
var item = db.Items.Find(Items);
return View(item);
}
A dropdown only sends simple values. The name usually have to match the name of the parameter in the method signature. Remember that it is the name attribute that is the key in the querystring, and not the id attribute. Hence the use of department in this example and Items in #Murali's answer.
Try changing your details method to:
public ActionResult Details(string department)
{
var item = db.Items.Find(department);
return View(item);
}

Ajax postbacks with cascading ListBox in ASP.NET MVC3

First off, I am an ASP.NET MVC noob. It's my first project with ASP.NET MVC, so I am still learning. My background is mostly in WPF and XAML for the past two years.
So here is my problem: I have three cascading ListBoxes. The second listbox data is dependent on the first, and the third is dependent on the second. I want to use Ajax refreshes to fill the data in each list.
Here is my Index.cshtml:
#model WebApplication.Models.DevelopmentModel
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
</head>
<body class="body" scroll="auto">
<div class="page">
<div class="content">
<div id="lists">
#Html.Partial("DevelopmentListsView", Model)
</div>
</div>
</div>
</body>
</html>
My DevelopmentListsView.cshtml looks like this:
#model WebApplication.Models.DevelopmentModel
#using (Ajax.BeginForm("Index", "Development", new AjaxOptions() { UpdateTargetId = "lists" } ))
{
#Html.ListBoxFor(m => m.SelectedApplication, new SelectList(ViewBag.Applications), new { onchange = "this.form.submit();" })
#Html.ListBoxFor(m => m.SelectedVersion, new SelectList(ViewBag.Versions), new { onchange = "this.form.submit();" })
#Html.ListBoxFor(m => m.SelectedFlow, new SelectList(ViewBag.Flows) )
}
My Model looks like:
public class DevelopmentModel
{
public string SelectedApplication { get; set; }
public string SelectedVersion { get; set; }
public string SelectedFlow { get; set; }
}
And my Controller looks like this:
public class DevelopmentController : Controller
{
//
// GET: /Development/
public ActionResult Index()
{
FillViewBag();
return View(new DevelopmentModel());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(DevelopmentModel model)
{
FillViewBag(model);
return PartialView("DevelopmentListsView", model);
}
private void FillViewBag(DevelopmentModel model = null)
{
//Magic to get all three lists dependent on the available data in the model:
ViewBag.Applications = applications;
ViewBag.Versions = versions;
ViewBag.Flows = flows;
}
}
Now, I want to use Ajax callbacks to retrieve the data, so it won't refresh every time, but when I click one of the Listbox items, the page then only shows the DevelopmentListsView view after that, not refreshing anything..
Can someone tell me what I am doing wrong?
Thanks for looking!
Figured out my own question:
I had two errors:
I missed the jquery script include in the Index.cshtml:
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
And I used the wrong submit (it should have been the jQuery submit):
$(this.form).submit()
The submit placed inside my models
#model WebApplication.Models.DevelopmentModel
#using (Ajax.BeginForm("Index", "Development", new AjaxOptions() { UpdateTargetId = "lists" } ))
{
#Html.ListBoxFor(m => m.SelectedApplication, new SelectList(ViewBag.Applications), new { onchange = "$(this.form).submit()" })
#Html.ListBoxFor(m => m.SelectedVersion, new SelectList(ViewBag.Versions), new { onchange = "$(this.form).submit()" })
#Html.ListBoxFor(m => m.SelectedFlow, new SelectList(ViewBag.Flows) )
}
Hope this helps someone some day ;).

Categories

Resources