I want to dynamically choose a partial view depending on what is sent to the controller, and have managed it but I feel that I am a) over complicating it and b) am unsure how to then easily get all page links. Each will be addressed in turn. Please ignore if I make a mistake below, it's from memory! At home it all works, I am after clarification on if there's a better way!
Firstly, what I've done:
I have a controller:
private Dictionary<int, string> pagesForFolder = new Dictionary<int, string>() {
... here I have my list of pages (index and pagename pairs)
}
public ActionResult Test(int id = -1)
{
try {
string pageName = "";
var result = pagesForFolder.TryGetValue(id, out pageName);
if(result)
string directiory = "~Views/folderA/_" + pageName +".cshtml";
return PartialView(directory);
}
catch(Exception ex) { ... }
return View();
}
Then in folderA I have all my partial views for that section of my website. I have many sections and 1 controller per section. However, I am thinking this won't scale out that great as I'll have to keep rebuilding every time I add a page. Would it be better to store the pages in the DB?
I think your current approach is quite reasonable. I think your problem as you mentioned is you have to rebuild your application whenever you add or remove new entry to the dictionary.
As a solution for this you can store your page names in db and retrieve when you needed.
Instead of having a hard coded dictionary try something like this...
In your controller..
public ActionResult Test(int id = -1)
{
try {
string pageName = GetYourPageFromDB(id);
if(!string.IsNullOrEmpty(pageName))
string directiory = "~Views/folderA/_" + pageName +".cshtml";
return PartialView(directory);
}
catch(Exception ex) { ... }
return View();
}
This may not be the exact answer try something like this...
Related
I need feature that is something similar to Laravel's old input helper but in MVC 5.
https://laravel.com/docs/5.6/requests#old-input
If validation fails, I need to reload all my model data as it was in the previous request except those inputs where user entered something wrong.
The problem is that my form has many disabled inputs and fields that program is fetching within [HttpGet] method, and they're getting lost during submission. So I need to store them in session.
The code below seems to work but is there any more efficient and beautiful way to do so with a less amount of code within each controller?
[HttpGet]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(Guid guid)
{
var model = new EditTaskViewModel();
model.Guid = guid;
await model.GetTaskFromRemoteService(new UserInfo(User));
ControllerHelpers.DisplayAlerts(model, this);
TempData["OldModel"] = model;
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(EditTaskViewModel model, Guid guid, string submit)
{
model.Guid = guid;
if (ModelState.IsValid) {
await model.UpdateTaskInRemoteService(new UserInfo(User), submit);
ControllerHelpers.DisplayAlerts(model, this, "Task successfully updated");
if (model.ErrorCode == null)
return RedirectToAction("Edit", new { guid = model.Guid });
return RedirectToAction("Index");
}
if (TempData["OldModel"] != null) {
model = (EditTaskViewModel)TempData["OldModel"];
}
return View(model);
}
Using session state (including TempData) like this may break when you have multiple copies of the page open. You can work around this by generating a unique ID for the session key and storing it in a hidden field.
However, I would try to avoid using session altogether.
A simple approach is to use hidden fields to store the values that aren't sent to the server because they are in disabled fields.
A more robust approach is a separate class (or at least a private method) that knows how to setup your model for the first time and in transition (e.g. failed server validation). I call these classes "composers" and I describe the approach here.
Pseudocode for how an action method with a composer might look:
if( ModelState.IsValid ){
return Redirect();
}
var rebuiltModel = _composer.ComposeEdit( incomingModel );
return View( rebuiltModel );
I think the answer was quite simple. The shortest and easiest way is to populate the object from the database\remote service once more.
The fields that user entered whether they're valid or not will stay as they were before. The rest of them will load once again.
I have a method that is looping through a list of values, what I would like to do is when I open the page to be able to see the values changing without refreshing the current view. I've tried something like the code bellow.
public static int myValueReader { get; set; }
public static void ValueGenerator()
{
foreach (var item in myList)
{
myValue = item;
Thread.Sleep(1000);
}
}
The actual thing is I want it to read this values even if I close the form. I presume I would need to assign a Task in order to do this, but I was wandering if there is any better way of doing it since it's a MVC application?
Here's another way to do it:
use AJAX and setTimeout
declare one action in your controller (this one will return your different values)
an integer in your ViewBag, some like: ViewBag.totalItems
Declare an action in your controller: This is important, because this will be your connection with your database, or data. This action will receive the itemIndex and will return that item. Something like this:
[HttpPost]
public JsonResult GetItem(int index) {
return Json(myList.ElementAt(index));
}
ViewBag.TotalItems: Your view has to know how many Items you have in your list. I recommend you to pass that value as an integer via ViewBag:
public ActionResult Index() {
ViewBag.TotalItems = myList.Count();
return View();
}
AJAX and setTimeout: Once that you have all of this, you're ready to update your view without refreshing:
<script>
$(function() {
var totalItems = #Html.Raw(Json.Encode(ViewBag.TotalItems));
var currentItemIndex = 0;
var getData = function() {
$.post("#Url.Action("GetItem")", {index:currentItemIndex}, function(data) {
// data is myList.ElementAt(index)
// do something with it
}).always(function() {
currentItemIndex++;
if(currentItemIndex < totalItems) {
setTimeout(getData, 1000); // get the next item every 1 sec
}
})
}
getData(); // start updating
})
</script>
Your best bet as #DavidTansey mentioned is to use SignlarR. It wraps web sockets and falls back to long polling/etc if the users' browser doesn't support it. Your users will subscribe to specific channels and then you can raise events in those channels.
With regard to your business logic, you'll need to look into async programming techniques. Once you start on this, you'll probably have more specific questions.
I have a problem transfering data from one view to another via the controler actions.
I the first view is a grid.mvc-Grid displayed. By select on row of the grid I get the ID for that object.
by transfering this to an action in the controler I try to filter the data. That works fine.
Here is the filter:
[HttpGet]
public ActionResult PersonenById(int id)
{
var personen = new ObservableCollection<Person>();
//Getting the data here :-)
foreach (DataRow r in access.Rows)
{
Person p = new Person();
//do some stuff
personen.Add(p);
}
//return PartialView("Personen", personen); //does not work
TempData["personen"] = personen;
return RedirectToAction("Personen"); // redirect to another view
}
In method II the view is filled:
public ActionResult Personen()
{
var persons = new ObservableCollection<Person>();
if (TempData["Persons"] == null)
{
}
return View(persons); //Works perfect
}
else
{
persons = (ObservableCollection<Person>) TempData["Persons"];
return View(persons);//does not redirect to that View
}
}
(Sorry for the strange formating. :-))
Is there any different way to send data from a view to another?
I tried:
return partial;
return View("Persons",persons);
and a lot other stuff.
You can redirect in a .cshtml view.
Eg:
Context.Response.StatusCode = 403;
Context.Response.Redirect(
$"{Context.Request.PathBase}/Error/403", false);
Should work like this:
return RedirectToAction("Personen", model);
Also, the "Personen" action should have the model as an argument, like this:
public ActionResult Personen(Person model) ...
LE: I have also noticed you have tried to send the data through the TempData object. Make sure the indexed object's name is the same (e.g. TempData["person"] everywhere)
Hope it answers your question.
With ASP.NET WebApi, when I send GET api/question?page=0&name=qwerty&other=params and API should give result within pagination envelop.
For that, I'd like to put result and change given page querystring to other values.
I tried as below code but I got a bad feeling about this.
protected HttpResponseMessage CreateResponse(HttpStatusCode httpStatusCode, IEnumerable<Question> entityToEmbed)
// get QueryString and modify page property
var dic = new HttpRouteValueDictionary(Request.GetQueryNameValuePairs());
if (dic.ContainsKey("page"))
dic["page"] = (page + 1).ToString();
else
dic.Add("page", (page + 1).ToString());
var urlHelper = new UrlHelper(Request);
var nextLink= page > 0 ? urlHelper.Link("DefaultApi", dic) : null;
// put it in the envelope
var pageEnvelope = new PageEnvelope<Question>
{
NextPageLink = nextLink,
Results = entityToEmbed
};
HttpResponseMessage response = Request.CreateResponse<PageEnvelope<Question>>(httpStatusCode, pageEnvelope, this.Configuration.Formatters.JsonFormatter);
return response;
}
The NextPageLink gives a lot complex result.:
http://localhost/api/Question?Length=1&LongLength=1&Rank=1&SyncRoot=System.Collections.Generic.KeyValuePair%602%5BSystem.String%2CSystem.String%5D%5B%5D&IsReadOnly=False&IsFixedSize=True&IsSynchronized=False&page=1
My question is,
My page handling with Dictionary approach seems dirty and wrong. Is there better way to address my problem?
I don't know why urlHelper.Link(routeName, dic) gives such a verbose ToString result. How to get rid of unusable Dictionary-related properties?
The key issue probably in your code is the conversion to the HttpRouteValueDictionary. New it up instead and add in a loop all key value pairs.
The approach can be simplified quite a lot, and you should also probably want to consider using an HttpActionResult (so that you can more easily test your actions.
You should also avoid using the httproutevaluedictionary and instead write your UrlHelper like
urlHelper.Link("DefaultApi", new { page = pageNo }) // assuming you just want the page no, or go with the copying approach otherwise.
Where just pre calculate your page no (and avoid ToString);
Write it all in an IHttpActionResult that exposes an int property with the page No. so you can easily test the action result independently of how you figure out the pagination.
So something like:
public class QuestionsResult : IHttpActionResult
{
public QuestionResult(IEnumerable<Question> questions>, int? nextPage)
{
/// set the properties here
}
public IEnumerable<Question> Questions { get; private set; }
public int? NextPage { get; private set; }
/// ... execution goes here
}
To just get the page no, do something like:
Source - http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21
string page = request.Uri.ParseQueryString()["page"];
or
you can use this extension method below (from Rick Strahl)
public static string GetQueryString(this HttpRequestMessage request, string key)
{
// IEnumerable<KeyValuePair<string,string>> - right!
var queryStrings = request.GetQueryNameValuePairs();
if (queryStrings == null)
return null;
var match = queryStrings.FirstOrDefault(kv => string.Compare(kv.Key, key, true) == 0);
if (string.IsNullOrEmpty(match.Value))
return null;
return match.Value;
}
I have been introduced to Razor as applied with MVC 3 this morning, so please forgive me if my question seems terribly uninformed!
I am working with an app whose workflow involves allowing a user to select a value (warehouse) from a drop down list, and add a record (material) from that warehouse to another record (Materials Request). Once the first material has been added to the Materials Request, I need to permanently set the value of the drop down to the warehouse that was first selected, then disable the drop down control (or set to read only, perhaps). The existing code in the razor file uses the DropDownListFor() method, including a ViewBag collection of Warehouse records. I have seen discussions which suggest abandoning the ViewBag design, but honestly I don't have the desire to rewrite major portions of the code; at least it looks like a major rewrite from the perspective of my experience level. Here's the original code:
#Html.LabelPlusFor(m => m.WarehouseId, "*:")
#Html.DropDownListFor(m => m.WarehouseId, (IEnumerable<SelectListItem>)ViewBag.WarehouseCodes, "")<br />
I believe I have been able to select a value based on a session object, though I'm still not sure how to disable the control. Here's my change:
#{
int SelectedWarehouseId = -1;
if (HttpContext.Current.Session["SelectedWarehouseId"] != null)
{
SelectedWarehouseId = Int32.Parse(HttpContext.Current.Session["SelectedWarehouseId"].ToString());
}
}
#Html.LabelPlusFor(m => m.WarehouseId, "*:")
#{
if (SelectedWarehouseId > -1)
{
#Html.DropDownListFor(m => m.WarehouseId, new SelectList((IEnumerable<SelectListItem>)ViewBag.WarehouseCodes, "WarehouseId", "WarehouseDescription", (int)SelectedWarehouseId))<br />
}
else
{
#Html.DropDownListFor(m => m.WarehouseId, (IEnumerable<SelectListItem>)ViewBag.WarehouseCodes, "")<br />
}
}
When the material is added to the Material Request, the WarehouseId is passed to the controller and I can access that value as "model.WarehouseId" in the controller class. However, I'm not sure how to get that value back to the View (apologies for the large code block here):
[HttpPost]
[TmsAuthorize]
public ActionResult Create(ItemRequestViewModel model)
{
string deleteKey = null;
//Removed code
else if (Request.Form["AddToRequest"] != null)
{
// If the user clicked the Add to Request button, we are only
// interested in validating the following fields. Therefore,
// we remove the other fields from the ModelState.
string[] keys = ModelState.Keys.ToArray();
foreach (string key in keys)
{
if (!_addToRequestFields.Contains(key))
ModelState.Remove(key);
}
// Validate the Item Number against the database - no sense
// doing this if the ModelState is already invalid.
if (ModelState.IsValid)
{
_codes.ValidateMaterial("ItemNumber", model.ItemNumber, model.WarehouseId);
Session["SelectedWarehouseId"] = model.WarehouseId;
}
if (ModelState.IsValid)
{
// Add the new Item Request to the list
model.Items.Add(new ItemViewModel() { ItemNumber = model.ItemNumber, Quantity = model.Quantity.Value, WarehouseId = model.WarehouseId });
ModelState.Clear();
model.ItemNumber = null;
model.Quantity = null;
model.WarehouseId = null;
}
}
//Removed code
return CreateInternal(model);
}
private ActionResult CreateInternal(ItemRequestViewModel model)
{
if (model != null)
{
if (!String.IsNullOrEmpty(model.SiteId))
{
ViewBag.BuildingCodes = _codes.GetBuildingCodes(model.SiteId, false);
if (!String.IsNullOrEmpty(model.BuildingId))
ViewBag.LocationCodes = _codes.GetLocationCodes(model.SiteId, model.BuildingId, false);
}
//Removed code
}
//Removed code
ViewBag.WarehouseCodes = _codes.GetWarehouseCodes(false);
return View("Create", model);
}
So my questions are, how do I disable the drop down list, and how can I pass a value for the selected WarehouseId back to the view? I've also considered adding the value to the ViewBag, but to be honest I don't know enough about the ViewBag to recognize any unintended consequences I may face by just randomly modifying it's contents.
Thanks for any help offered on this.
Without going into which approach is better...
Your dropdown should be rendered as an HTML select element, in order to disable this you'll need to add a disabled="disabled" attribute to it.
The DropDownListFor method has a htmlAttributes parameter, which you can use to achieve this:
new { disabled = "disabled" }
when your pass model to your view like
return View("Create", model);
if WareHouseID is set in model then
Html.DropDownListFor(x=>x.WareHouseID, ...)
will automatically set the selected value and u don't have to do that session processing for this. So far as disabling a field is required, stewart is right. you can disable drop down this way but then it won't be posted to the server when u submit the form. you can set it to readonly mode like
new{#readonly = "readOnly"}