In my MVC 5 app I need to be able to dynamically construct a list of fully qualified external URL hyperlinks, alone with some additional data, which will come from the Model passed in. I figure - I will need to construct my anchor tags something like this:
{{linkDisplayName}}
with AngularJS this would be natural, but, I have no idea how this is done in MVC.
Is there a templating library that can be used for this?
1) Create a model to Hold the Links
public class LinkObject
{
public string Link { get; set; }
public string Description { get; set; }
}
2) In your Action you can use ViewBag, ViewData or even pass the list inside you Model. I will show you how to do using ViewBag
public ActionResult MyDynamicView()
{
//Other stuff and code here
ViewBag.LinkList = new List<LinkObject>()
{
new LinkObject{ Link ="http://mylink1.com", Description = "Link 1"},
new LinkObject{ Link ="http://mylink2.com", Description = "Link 2"},
new LinkObject{ Link ="http://mylink3.com", Description = "Link 3"}
};
return View(/*pass the model if you have one*/);
}
3) In the View, just use a loop:
#foreach (var item in (List<LinkObject>)ViewBag.LinkList)
{
#item.Description
}
Just create a manual one for that, no need to do it from a template. For example, in javascript
function groupAnchor(url,display){
var a = document.createElement("a");
a.href = url;
a.className = "list-group-item";
a.target = "_blank";
a.innerHTML = display;
return a;
}
And then use that function to modify your html structure
<div id="anchors"></div>
<script>
document.getElementById("anchors").appendChild(groupAnchor("http://google.com","Google"));
</script>
Your approach to modification will more than likely be more advanced than this, but it demonstrates the concept. If you need these values to come from server side then you could always iterate over a set using #foreach() and issue either the whole html or script calls there -- or, pass the set from the server in as json and then use that in a function which is set up to manage a list of anchors.
To expand on this, it is important to avoid sending html to the view from a razor iteration. The reason being that html constructed by razor will increase the size of the page load, and if this is done in a list it can be a significant increase.
In your action, construct the list of links and then serialize them so they can be passed to the view
public ActionResult ViewWithLinks()
{
var vm = new ViewModel();
vm.Links = Json(LinkSource.ToList()).Data;
//or for a very simple test for proof of concept
var Numbers = Json(Enumerable.Range(0,100).ToList()).Data;
ViewData["numbers"] = Numbers ;
return View(vm);
}
where all you need is an object to hold the links in your view model
public class ViewModel
{
public ICollection<Link> Links { get; set; }
}
public class Link
{
public string text { get; set; }
public string href { get; set; }
}
and then in your view you can consume this json object
var allLinks = #Html.Raw(Json.Encode(Model.Links));
var numbersList = #Html.Raw(Json.Encode(ViewData["linkTest"]));//simple example
Now you can return to the above function in order to place it on the page by working with the array of link objects.
var $holder = $("<div>");
for(var i = 0; i < allLinks.length; i++){
$holder.append(groupAnchor(allLinks[i].href,allLinks[i].text));
}
$("#linkArea").append($holder);
The benefit is that all of this javascript can be cached for your page. It is loaded once and is capable of handling large amounts of links without having to worry about sending excessive html to the client.
Related
I posted the question earlier, but didn't receive any correct responses, hence posting again with some edits. I have a function that accepts two parameters, IDs and Dates. When I had put breakpoints, I was able to see the Ids and the Dates selected on the page as parameter values. However, after hitting the process button, nothing happens, meaning this data isn't getting saved to the DB.
Model Classes:
public class Hello{
public string ID{ get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime? Date{ get; set; }
}
Controller Class:
[HttpGet]
public ActionResult Selection(string ids, string dates)
{
model = new Hello();
ExtensionDB db = new ExtensionDB();
string[] IDS = ids.Split(',');
string[] DATES = dates.Split(',');
List<Hello> list = new List<Hello>();
for (int i = 0; i < IDS.Length; i++)
{
if (IDS[i] != null && IDS[i] != "")
{
Hello item = new Hello { ID = IDS[i], Date = DateTime.Parse(DATES[i]) };
list.Add(item);
}
}
if (ModelState.IsValid)
{
foreach (var row in db.Table1)
{
foreach (var row2 in db.Table2)
{
if (row.UID== row2.CID) // UID and CID are Foreign keys that join these two tables
{
foreach (var item in list)
{
if (row.UID == Convert.ToInt32(item.ID))
{
row2.ReportedDate = item.Date;
}
db.SaveChanges();
}
}
}
}
ViewBag.Message = "Success";
return View(model);
}
else
{
ViewBag.Message = "Failed";
return View(model);
}
}
I will add the view class if needed, however the problem is here.. You can also refer to it here: Saving changes to the DB MVC
Your code does not attempt to update anything. Start with confirming what the data you are passing to this POST call contains, and what you want to do with it. It looks like what you are trying to do is update the dates for a number of records. Looking at your previous post (no need to re-post another question with the same code) there are a few things..
First: Structure the data you want to pass to the POST call into a collection of simple objects containing an id and a date. For example:
{
id = rid,
date = date
}
and add those to the collection named something like "updateData" rather than two separate arrays of IDs and dates. Then in the server-side code, declare a simple view model class:
public class UpdateDateViewModel
{
public int Id { get; set; }
public DateTime Date { get; set; }
}
In the ajax call instead of:
data: { ids: ids, dates: dates },
you'll want something like:
data: { updates: updateData },
where updateData is your collection of id + date pairs.
and use that view model in your method:
public ActionResult Process(IList updates)
Provided that request data is sent as Json, ASP.Net should translate that data automatically for you, though you may need to configure ASP.Net to translate the camelCase vs PascalCase. Worst case, to test, you can use camelCase property names ("id" and "date")
Now when it comes to updating the data: Server side, please get in the habit of using meaningful variable names, not "c", "i", etc. It makes code a lot easier to understand.
public ActionResult Process(IList<UpdateDateViewModel> updates)
{
using (db = new DB())
{
//rp = new RequestProcess(); - Assuming RequestProcess is an Entity?
//var c = rp.getStuff(); - No idea what this getStuff() method does...
foreach(var update in updates)
{
var request = db.RequestProcesses.Find(update.Id);
if (request != null)
request.RequestDate = update.Date; // If we find a matching request, update it's date.
else
{ // Doesn't exist, create it and add it to the DbSet.(table)
var request = new RequestProcess { Id = update.Id, RequestDate = update.Date };
db.RequestProcesses.Add(request);
}
db.SaveChanges();
}
}
}
Now this is a very bare bones guess at what you may be trying to do. Ideally though, updates should be completely separate from adds in the sense that an update should only deal with existing records. If it comes across an ID that it cannot find it should throw an error, ignore, and/or return a status to the user that something wasn't right. Creating new entries should be a separate call and ensure that records are properly initialized with their required fields.
Your original code looked to be taking a list of IDs, but then creating a new entity and calling that "getStuff" method that didn't have the DbContext, or any of the values from the POST call, but then attempting to copy values from that entity into the string parameters that you passed (which would overwrite the Json string) None of that would have updated an entity which would never have updated your data.
Take it slow and follow the examples before attempting to adapt them to your ideas. It will be a lot more constructive and less frustrating then writing a bunch of code that doesn't really make much sense, then wondering why it doesn't work. Your original code has probably a dozen or more problems and inefficiencies. Simply pasting it up on Stack will get a lot of confusing comments based on these problems which don't really help with the first issue you want to solve. Strip it back to the minimum, start with getting the data you need to the server in a meaningful way, then from that, attempt to use that data to update your entities.
So here's the deal, i want to be able to export any Enumerable of items to excel:
Here's an ActionMethod in some Area of my app that constructs an "ExportToExcel" model, then Redirects it to an Action Method in another controller and another are which does all the formatting-to-excel work:
public ActionResult ExportCustomListToExcel()
{
var exportModel = new ExportToExcelModel();
//Here I fill up the model with a dataTable / other file info like
//exportModel.Items = blah blah..
return RedirectToAction("ExportToExcel", "Shared", new { model = exportModel, testString = "test", area = "Shared" });
}
And here's my Shared ExportToExcel ActionMethod:
public ActionResult ExportToExcel(ExportToExcelModel model, string testString)
{
//PROBLEM IS RIGHT HERE!
// where testString == "test"
// but model == null :(
//Ommited unrelated code
}
My ExportToExcel actionMethod gets hit, but somewhere along the way my ExportToExcelModel gets lost :(
Note: It succeeds on passing strings like "testString" so is there somwthing wrong with my model?
Just in case, the ExportToExcelModel is:
public class ExportToExcelModel
{
public ExportToExcelModel() {}
public ExportToExcelModel(string fileName, ItemType itemType, IEnumerable<ExportableToExcelItem> items)
{
this.FileName = fileName;
this.ItemType = ItemType;
this.Items = items;
}
public string FileName { get; set; }
public ItemType ItemType { get; set; }
public IEnumerable<ExportableToExcelItem> Items { get; set; }
}
Thanks in advance!
First time i've ever needed to actually ask a question here since every other question i've ever had i've found already answered here :)
EDIT: Posting FormCollection results:
http://imageshack.us/photo/my-images/861/sinttulonsa.png
Sorry, newbies cant post pics :(
The reason is that a RedirectToAction result will launch a GET request and your parameters will have to be passed along through the querystring. Obviously there is a limit to the amount of characters a url can consist of.
Seems to me that you should do the conversion to Excel in a class instead of behind another Action.
So CustomExportAction1 and CustomExportAction2 both call
return File(ExcelExporter.ExportExcel(dataToExport));
or something similar.
try to switch your ExportToExcel signature to
public ActionResult ExportToExcel(FormCollection data)
{
var model = new ExportToExcelModel();
try
{
UpdateModel(model, data)
}
catch(UpdateModelException ex)
{
}
}
look at what's in the FormCollection (that might help), and also see if UpdateModel is throwing an exception, because this is what is happening behind the seen when you make your action method take in a model instead of a FormCollection.
Hope that help you track it down
UPDATE:
You might have to do it using TempData, read this, supposedly you can't do this out of the box with ASP.NET MVC!!
I am using ASP.NET MVC 3 with razor. I am also using the latest version of Telerik MVC.
I have a grid on my view displaying a list of applications. Each application has a state. I need to write a helper method to display links in each row of the grid depending on each application's current state. If the state is 1 then I need to display an Edit link. The helper that I have looks like this:
public static string ActionLinks(this HtmlHelper helper, string grantApplicationId, string grantApplicationStateId)
{
List<TagBuilder> linkList = new List<TagBuilder>();
string actionLinks = string.Empty;
if (grantApplicationStateId == "1")
{
// Edit link
TagBuilder editLink = new TagBuilder("a");
editLink.MergeAttribute("href", "/GrantApplication/Edit/" + grantApplicationId);
editLink.InnerHtml = "Edit";
linkList.Add(editLink);
}
foreach (TagBuilder link in linkList)
{
actionLinks += link.ToString() + "<br>";
}
return actionLinks;
}
The grid column in my Telerik grid looks like this:
column.Bound(x => x.Id)
.ClientTemplate(#Html.ActionLinks("<#= Id #>", "<#= GrantApplicationStateType.Id #>"))
.Title("Actions");
My view model looks like:
public class GrantApplicationListViewModel
{
// Just partial properties
public GrantApplicationStateType GrantApplicationStateType { get; set; }
}
And GrantApplicationStateType looks like:
public class GrantApplicationStateType : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
When the above helper method is called then the value of grantApplicationStateId is "<#= GrantApplicationStateType.Id #>". How would I get the value that was passed through? What I mean is, if the value is 1 that was passed through, how would I get 1 because currently it is "<#= GrantApplicationStateType.Id #>"?
UPDATE 2012-02-06
I tried Darin's link, used the exact same sample in my code, but changed the following:
column.ActionLink("Open", "Edit", "GrantApplication", item => new { id = item.Id, applicationStateId = item.GrantApplicationStateType.Id });
I need to pass through 2 values. I need to do a couple of if statements on the grant application state id, and then return the specific action links to the client. But it fails when looping through the values in:
if (memberExpression.Expression is ParameterExpression)
value = string.Format("<#= {0} #>", memberExpression.Member.Name);
else
value = GetValue(memberExpression);
The first parameter passed in goes through the first/true part of the if statement:
value = string.Format("<#= {0} #>", memberExpression.Member.Name);
..but the second parameter goes through the false part of the if:
value = GetValue(memberExpression);
What's the difference between the 2?
And then it fails at the GetValue method with the following message:
variable 'item' of type MyProject.ViewModels.GrantApplicationListViewModel' referenced from scope '', but it is not defined
I can't get this to work, and I looked for some more samples and couldn't find any.
You can't do this using a helper. In ASP.NET MVC helpers run on the server. Notice the ClientTemplate name in the Telerik grid? That is meant to run on the client.
What it does is that it simply uses <#= Id #> as a placeholder to a server side helper which will generate some HTML and on the client side, the Telerik grid will do a simple string replace in order to put the value which is only known on the client.
At the moment your server side ActionLinks helper is invoked, the Telerik grid cannot pass you the actual value which is known only on the client.
You may take a look at the following blog post for a nice extension.
I have built a simple MVC3-based ticket entry site for a less-than-usable call center application and am attempting to refactor my prototype to better adhere to design patterns partly to make it more maintainable going forward but mostly as a learning exercise.
The user-facing view is a form consisting of basic user information in addition to a few panels allowing selection of various resource types. Each resource type (hardware, software, etc) is displayed in the same way: using dual, filterable listboxes with add/remove buttons, an optional “justification” textarea that conditionally displays if a requested resource requires justification, and general comments.
I have built the following ViewModel for the individual panels:
public class RequestableList
{
// list of requestable items ids requiring justification
private List<string> _restrictedItems = new List<string>();
public List<string> RestrictedItems
{
get { return _restrictedItems; }
set { _restrictedItems = value; }
}
// the key-value pairs from which to populate available items list
private Dictionary<string, string> _availableItems = new Dictionary<string, string>();
public Dictionary<string, string> AvailableItems
{
get { return _availableItems; }
set { _availableItems = value; }
}
// item ids requested by user
private List<string> _requestedItems = new List<string>();
public List<string> RequestedItems
{
get { return _requestedItems; }
set { _requestedItems = value; }
}
}
The main ViewModel is then comprised of multiple RequestableLists as necessary:
public class SimpleRequestViewModel
{
public UserInfo userInfo { get; set; }
public RequestableList Software {get;set;}
public RequestableList Hardware {get;set;}
public RequestableList Access {get;set;}
public string SoftwareAdditionalInfo { get; set; }
public string HardwareAdditionalInfo { get; set; }
public string AccessFileMailShare { get; set; }
public string AccessAdditionalInfo { get; set; }
public string SoftwareJustification { get; set; }
public string HardwareJustification { get; set; }
public string AccessJustification { get; set; }
public string Comment { get; set; }
}
I have created a strongly typed view for SimpleRequestViewModel (and its variant) and a strongly typed EditorTemplate for RequestableList that wires up the dual listboxes, filtering, and jquery. All renders well and is working but the code currently smells.
When posting to the controller, if the model is valid I must translate it into a readable text description in order to create a new ticket in in the call center app. It doesn’t feel right to have the controller performing that translation into readable text but I run into hurdles when trying to design another class to translate the viewmodels.
Only the selected item values are posted so before translating the request into text I must first lookup the appropriate text for the provided values (they are required in description). The controller is currently the only object that has access to the call center data model for this lookup query.
There are 2 similar ViewModels containing varying combinations of RequestableLists so any translator must be able to translate the various combinations. One has only Hardware and Software, another may have Hardware Software, and a few more RequestableLists.
I considered overriding ToString() directly in the ViewModel but didn’t like that business logic (conditional rendering) there, and again, once posted, the ViewModel doesn’t contain the text for the selected items in the listbox so it would need access to the data model.
The translation of posted values to text as it is currently handled in the controller smells as it’s handled in a switch statement. The controller takes each posted RequestableList and populates the original “Available” fields before it builds the new ticket description.
switch (requestCategory)
{
case RequestableCategory.Software:
itemList = sde.GetSoftware();
break;
case RequestableCategory.Hardware:
itemList = sde.GetHardware();
break;
case RequestableCategory.Access:
itemList = sde.GetAccess();
break;
case RequestableCategory.Telecom:
itemList = sde.GetTelecom();
break;
default:
throw new ArgumentException();
}
So, my question(s):
What patterns are techniques would you recommend for performing the posted viewmodel to ticket description translation?
How do you typically handle the “only posts value” issue with select boxes when you need the text as well as the value?
Is there a better way for me to be approaching this problem?
Again, I am hoping this is a learning experience for me and am more than willing to provide additional information or description if needed.
A few suggestions:
Abstract the logic that does the call center submission into its own class. Provide (from the controller) whatever dependencies it needs to access the call center DB. Have different methods to handle the various types of view models using overloading. Presumably the descriptions come from the DB so you can extract the description from the DB based on the value in this class. This class could also take responsibility for building your view models for the display actions as well. Note that with this pattern the class can interact with the DB directly, through a repository, or even via web services/an API.
Use a repository pattern that implements some caching if performance is an issue in looking up the description from the DB the second time. I suspect it won't be unless your call center is very large, but that would be the place to optimize the query logic. The repository can be the thing that the controller passes to the submission class.
If you don't need to access the DB directly in the controller, consider passing the broker class as a dependency directly.
It might look like:
private ICallCenterBroker CallCenterBroker { get; set; }
public RequestController( ICallCenterBroker broker )
{
this.CallCenterBroker = broker;
// if not using DI, instantiate a new one
// this.CallCenterBroker = broker ?? new CallCenterBroker( new CallCenterRepository() );
}
[HttpGet]
public ActionResult CreateSimple()
{
var model = this.CallCenterBroker.CreateSimpleModel( this.User.Identity.Name );
return View( model );
}
[HttpPost]
public ActionResult CreateSimple( SimpleRequestViewModel request )
{
if (Model.IsValid)
{
var ticket = this.CallCenterBroker.CreateTicket( request );
// do something with ticket, perhaps create a different model for display?
this.CallCenterBroker.SubmitTicket( ticket );
return RedirectToAction( "index" ); // list all requests?
}
return View();
}
I want to write a custom view engine that returns custom text (like coma delimited) does anyone know how I'd change the view engine on the fly to handle this?
I'd create a custom ActionResult. I use Json() function to return a JsonResult when I need JSON as response. I use this code to fill a ExtJS tree using JSON data.
public JsonResult Folders(string node)
{
var relativePath = (node == "root") ? "" : node;
var path = Path.Combine(BASE_PATH, relativePath);
var folder = new DirectoryInfo(path);
var subFolders = folder.GetDirectories();
var folders = new List<ExtJsTreeNode>();
foreach (var subFolder in subFolders)
{
folders.Add(new ExtJsTreeNode(subFolder.Name, subFolder.FullName.Replace(BASE_PATH, ""), "folder"));
}
return Json(folders);
}
private class ExtJsTreeNode
{
public string text { get; set; }
public string id { get; set; }
public string cls { get; set; }
public ExtJsTreeNode(string text, string id, string cls)
{
this.text = text;
this.id = id;
this.cls = cls;
}
}
A sample of a custom ActionResult here.
Your controller shouldn't know or care about this, other than which View to send the data to. The View can render in any format imaginable. I've got views that emit RSS (XML), etc. In the controller, either send it to the default view or explicitly identify the target view.
If I understood your question correctly, you want to use different views based on the parameters passed to the controller. If so, you can use this statement in the controller action:
return View("ViewName");
Otherwise, please clarify your question.