I've got a nice MVC app running now, and I'm adding some AJax functionality. I have a table which displays 10 items, only certain users can see certain items. When the user adds a new post I've set-up the ajax to save the new entry.
However I then need to update the table, I cant find out from JQuery what status the user is (And hence what they can see) so I cant just insert a new row (As some users cant see that row). If this was web-forms I would likly have a page that dumps the table and then i would use JQuery to load the contents of that trimed down 'page' into the relevent slot on the current page.
Whats the best way to achieve this with MVC?
Thanks
As #Robert Koritnik suggests, the best way to handle this is using a PartialView. I would suggest having two separate controller actions -- one that handles the original request and another that handles the AJAX new entry. Both actions would call the same logic to get the data for the table. The former would put the data into the view model along with other page data. The latter would package the data into a model for the partial view.
Model classes
public class PageViewModel
{
....
public IEnumerable<TableViewModel> TableData { get; set; }
}
public class TableViewModel
{
...
}
Controller Code
[AcceptVerbs( HttpVerbs.Get )]
public ActionResult Index()
{
var model = new PageViewModel();
model.TableData = GetTableForUser( this.User );
return View( model );
}
[AcceptVerbs( HttpVerbs.Post )]
public ActionResult AddEntry( ... )
{
... add the new entry ...
var model = GetTableForUser( this.User );
return PartialView( "TableView", model );
}
private TableViewModel GetTableForUser( IIdentity user )
{
...
}
View Code
Main View
<% Html.RenderPartial( "TableView", model.TableData ); %>
<script type="text/javascript">
$('#entryForm').submit( function() {
$.post( '<%= Url.Action( "addentry", "controller" ) %>',
$('#entryForm').serialize(),
function(data) {
$('#table').replaceWith( data );
},
'html' );
return false;
});
</script>
TableView
<table id="table">
<% foreach (var row in Model) { %>
<tr>
...
</tr>
<% } %>
</table>
Use PartialView functionality that will return just the <table> you need. In your main page it will be included but in your Ajax call it will only emit HTML back to the client that you can use to replace existing <table> element.
If I understand you correctly, you're saying that user A is viewing a list of entries and user B (In some other part of the world. maybe) posts a new entry. You want the list on user A's screen to update, but only if the new entry is one that user A is allowed to see?
If this is the case, you can have a timer running on the page and, when the timer triggers, it causes an AJAX call to the server, asking if there are any new entries for the user. The identity of the user and - therefore - which items they can see, should be determined from the session (Exactly how this works depends on your particular architecture, but I'd guess that you're already doing this, to display the list of items for user A to start with)
There are all sorts of fine details to consider here...
How often should the timer trigger, in order to get updates in a timely manner, but without causing too much traffic to the server
Should you simply update the entire list (This makes the code simple) or should you only download new items (This makes the logic more complicated, but also keeps the traffic smaller)
How do you ensure that you correctly identify the user, and correctly filter the entries to show only the relevant ones.
It's a relatively simple scenario - and ones that's no too uncommon - but it needs to be approached in a sensible manner to prevent complication setting in.
It's not hard to add a cookie which will store the current status of the user, or you can simply add another Ajax call to find out whether the user is authorized or no. you simply create some items in your controller to handle all the situations: whether user is authorized or no, what you show/hide from them.
what exactly do you need thou?
Related
I've got an MVC controller that can be called via form submission in a couple different places. The controller then renders a view whose primary purpose is to allow the user to send the document or post it to an external site, as well as fill in text fields that will be used in the notification email.
I am performing validation on these fields - the user can enter custom subject/body text. If they do not, they will receive a popup alert and can either return to the form or submit it using default text indicated in the placeholder value.
The problem is that when the user first reaches this page and clicks the send button, no input in the textboxes is actually registering and it gives the empty string notification regardless of what is actually in the fields; however, if I hit F5 and try again, the input works perfectly.
I feel like this has something to do with the form submissions that initially call this controller being done via POST action, whereas it seems to work fine with the GET on page refresh. I just can't figure out how to either get the content to respond properly when the controller is called via POST, or how to submit the form without posting the data.
Thanks in advance for reading and any help.
Here is the calling controller:
public ActionResult Index(FormCollection collection)
{
//modelbuilding code
return View (Model);
}
The code that calls the controller always uses this format: (in this case, it would be called from the Recipients/Index view.
#using(Html.BeginForm("Index", "Distribution", FormMethod.Post )) {
//form values
<input type="submit" data-role="button" value="Done"/>
}
Here is the relevant part of the view and the JS validation function:
<div id="SubjectTemplate">
<p>Subject: <input id="emailSubjectTextBox" name="EmailSubject" placeholder="#EmailSubject" /></p>
</div>
Send Notification
<script>
function validateInput() {
var possibleErrors = [];
if (!(document.getElementById('emailSubjectTextBox').value)) {
possibleErrors.push('#incompleteEmailSubject' + '\n');
}
//more validation that works the same way and has the same problem
if (possibleErrors.length > 0) {
if (confirm(possibleErrors))
{
window.location.href = '#Url.Action("Send")'
}
}
else {
window.location.href = '#Url.Action("Send")'
}
}
</script>
I'm not sure I fully understand your question, but generally speaking you should not use the same action for POST and GET.
Even more importantly, you should not be using POST if your action does not have some kind of side effect. If all you are doing with your form submission is making some kind of choice then you should be using GET.
See the following post for more information and examples of how to perform ajax GET requests using jQuery: http://www.jquery4u.com/ajax/key-differences-post/
Regardless of what you are trying to do, it is very good practice that POST calls perform some action, and then redirect to a GET action which returns to the user.
[HttpPost]
public ActionResult Index(FormCollection collection)
{
//do whatever you need to save the record
return RedirectToAction("Index")
}
[HttpGet]
public ActionResult Index()
{
//modelbuilding code
return View (Model);
}
Not only does this help your controller adhere to a RESTful methodology, it also stops the user getting that annoying 'Are you sure you want to resubmit this page' if they happen to press f5
Hope this helps.
I found the problem resulted from jQueryMobile automatically utilizing AJAX to post forms. I solved this by including new {data_ajax = "false"} to the Html.BeginForm statements.
My application needs to do an HTTP post of a table with checkboxes like in the image above. On the controller side I will need to traverse the table and perform certain operations for each row that was checked.
The things that I need to do are:
Identify whether a row is checked
Get the cell values of a checked row
I have a good understanding on how this will be done in Razor in as far as posting the form is concerned. But I am clueless once I am in my controller's action method.
Please help. Thanks.
From what you've show, it appears that all you really need in your action method is a collection of ids to identify which "rows" to modify. I'd use a series of checkboxes with values set to the id of the row they represent. Presumably you have some sort of persistence mechanism in which these rows can be looked up or have them cached server side.
[HttpPost]
public ActionResult Update( List<int> rowIDs ) // where your checkboxes are named rowIDs
{
var messages = DB.Messages.Where( m => rowIDs.Contains( m.ID ) );
foreach (var message in messages)
{
// process the update
}
DB.SaveChanges();
return RedirectToAction( "index" ); // display the updated list
}
Note that it's more likely that you have a model with the collection of ids as well as some other data representing what "update" to perform. Posting collections can be tricky; you might need to play with the name of the input and/or with hidden indexes if you're not getting all the data posted back as expected.
Working on an ASP.NET MVC 3 (Razor) application, that's mainly concerned with UGC (user generated content).
I'm working on a "Q&A" area - where users can ask questions, others can answer, vote, etc.
As such, i'm trying to figure out a clean way to handle the available operations a user can do on any given page, based on their role and other factors.
Take the scenario of the "Question Detail Page" (like this page on Stack Overflow).
Any (authenticated) user can:
Vote for question/answer
Answer
Question owner can:
Edit
Delete
Mark answer
And so forth.
Now, i have a QuestionViewModel, that is used to display the question and relevant answers for this particular View.
I create it using AutoMapper.
How can i display "stickies" (e.g hyperlinks) on the page, based on the available operations?
My current thinking is:
I create an enum: QuestionOperation (Answer, Edit, Disable, Vote, Answer, etc)
I add a property of type IEnumerable<QuestionOperation> to my ViewModel
I set this property in my action method (HTTP GET), checking if the user is authenticated and the roles they are a part of.
I then use an editor template to render out each operation as a hyperlink, using Html.ActionLink
Is that considered a clean approach - or can anyone suggest a better one?
Keeping in mind i am re-using this QuestionViewModel on three pages:
The question detail page
The "Ask a question" page
The "Edit a question" page
So because these operations are page/user dependant, it can't really be done with AutoMapper.
I would setup a separate controller and action which will be returning a partial view containing the necessary links. Then I would use the Html.Action helper to include it from the main view.
Something among the lines:
public class UserLinksController: Controller
{
// TODO: ctor DI of a repository, etc...
public ActionResult Index(string questionId)
{
string username = User.Identity.IsAuthenticated
? User.Identity.Name : string.Empty;
var roles = _repository.GetRolesForQuestion(username, questionId);
var model = Mapper.Map<UserRoles, RolesViewModel>(roles);
return PartialView(model);
}
}
and in the corresponding partial you would check the view model and render the necessary links:
#model RolesViewModel
#if(Model.CanEdit)
{
#Html.ActionLink("Edit", "Edit", "Questions")
}
#if(Model.CanDelete)
{
#Html.ActionLink("Delete", "Delete", "Questions")
}
...
Now somewhere in your main view simply include this action using the Html.Action method:
#Html.Action("Index", "UserLinks", new { questionId = Model.QuestionId })
For some reason I'm stuck on this. I need to filter results from a View based on a DropDownList in the same view. The basic idea is this: I have a list of providers that belong to various partners, but the provider list contains ALL the providers together (for all partners). I need to be able to display the providers by partner when someone wants to see just that partner (otherwise, the default listing will be ALL providers). My view currently is the "default" (showing all), but for some reason Im sitting here staring at the monitor (for the last 2 hours!) trying to figure out how to filter these results.
Any suggestions where to start/how to do it?!
EDIT: If you want to do this with jQuery and AJAX (which will provide a better user experience because only the subdivisions list will refresh), see this tutorial.
If I understand correctly, you basically want to do a WebForms-style postback.
Let's say you have a control with countries and country subdivisions (e.g. states, provinces, etc). When the country changes, you want the appropriate subdivisions to display.
So this would be view:
<% using (Html.BeginForm()) { %>
<%=Html.DropDownList("Address.CountryId", new SelectList(Country.GetAll(), "Id", "Name"), new { onchange = "this.form.submit();" })%>
<%=Html.DropDownList("Address.CountrySubdivisionId", new SelectList(CountrySubDivision.GetByCountryId(Model.CountryId), "Id", "Name"))%>
<input type="submit" name="btnSubmit" value="Submit"/>
<%} %>
This is the key to getting the dependent list to filter:
new { onchange = "this.form.submit();" }
And in the controller, you'd have something like this:
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Index(string btnSubmit)
{
if (btnSubmit == null)
{
// return the view displayed upon GET
}
else
{
// process the submitted data
}
}
In the above code, if the form submission was triggered by changing the value in a dropdown, btnSubmit will be null. Thus, the action you are POSTing to can tell whether or not the user meant to finalize her changes.
To add upon the earlier answers.
To create a drop down (in ASP .NET MVC 3) I did the following:
Add code to Index.cshtml
#using (Html.BeginForm())
{
#Html.DropDownList("EmployeeId", (SelectList)ViewData["EmployeeId"])
<input type="submit" name="btnSubmit" value="Submit"/>
}
Add code to YourModelNameController.cs in the default ActionResult for Index()
public ActionResult Index()
{
//create a selectlist
var employeeList = from el in db.Employee select el;
ViewData["EmployeeId"] = new SelectList(employeeList, "EmployeeId", "TmName");
return View(modelName);
}
There are many ways to skin this cat. Here's one.
Enclose your DropDownList in a form with METHOD=GET.
<form action="" method="get">
<select name="provider">
<option>1</option>
<!-- etc -->
</select>
</form>
Then, in you controller, filter based on the value of provider that was passed in. Remember to treat it as a Nullable parameter so that you can have some kind of behavior when it's empty.
Without posting some of your current code, it's tough to get much more specific than that.
Let's assume that you're probably passing a model to the view and that model is a list or IEnummerable of partners. What you want to do is restrict the list. In order to do that add a drop down list in the view and fill it with some possible partners. This can be done either by putting a list in ViewData or expanding the model passed back to the view. Both have advantages. Now when you change the drop down reload the page but append a parameter which is the filter. In the controller check for that parameter in the action, if it isn't present then return an unfiltered list, if it is then apply a filter and return the list. The view will just dumbly display whatever you give it.
As for the filtering you might want to try using LINQ.
You probably want a parameter to your controller action, maybe a (nullable?) id of the provider, to filter the results already when you get them from DB. Then just use the same view to list them, and request a new list if the dropdownlist changes.
Best solution I know is that one.
http://gridmvc.codeplex.com/SourceControl/latest
I'm new to MVC, so please bear with me. :-)
I've got a strongly typed "Story" View. This View (story) can have Comments.
I've created two Views (not partials) for my Comments controller "ListStoryComments" and "CreateStoryComment", which do what their names imply. These Views are included in the Story View using RenderAction, e.g.:
<!-- List comments -->
<h2>All Comments</h2>
<% Html.RenderAction("ListStoryComments", "Comments", new { id = Model.Story.Id }); %>
<!-- Create new comment -->
<% Html.RenderAction("CreateStoryComment", "Comments", new { id = Model.Story.Id }); %>
(I pass in the Story id in order to list related comments).
All works as I hoped, except, when I post a new comment using the form, it returns the current (parent) View, but the Comments form field is still showing the last content I typed in and the ListStoryComments View isn’t updated to show the new story.
Basically, the page is being loaded from cache, as if I had pressed the browser’s back button. If I press f5 it will try to repost the form. If I reload the page manually (reenter the URL in the browser's address bar), and then press f5, I will see my new content and the empty form field, which is my desired result.
For completeness, my CreateStoryComment action looks like this:
[HttpPost]
public ActionResult CreateStoryComment([Bind(Exclude = "Id, Timestamp, ByUserId, ForUserId")]Comment commentToCreate)
{
try
{
commentToCreate.ByUserId = userGuid;
commentToCreate.ForUserId = userGuid;
commentToCreate.StoryId = 2; // hard-coded for testing
_repository.CreateComment(commentToCreate);
return View();
}
catch
{
return View();
}
}
The answer is to use return RedirectToAction(). Using this enforces the PRG pattern and achieves my goal.
My earlier comment to my original post did cause an error, that I guess I'm still confused about, but this works:
return RedirectToAction("Details", "Steps", new { id = "2" });
I, and this is a personal opinion, think you've tackled this the wrong way.
Personally I would;
Create a view called ListStories.
Create a partial view that lists the
stories.
Create a partial view to create a
story.
When you want to add a story, simply
show the add story html.
Then when the user presses a button
you do a jQuery postback, add the
new story and return a PartialView
of either the new story or all the
stories.
If you return a partial view of all
stories then replace the bounding
div that contains all the stories
with the new data.
If you return only a single story
then append it to the end of the div
containing the stories.
I know this means a lot of re-work and it sounds complex and like a lot of work but doing it like this means greater flexibility later on because you can re-use the partial views or you can make a change once and all views using that partial view are now updated.
also, using jQuery means that the adding of stories looks seemless w/out any obvious post back which is nice.
Since the problem seems to be caching, you can simply disable/limit caching.
Add the following attribute to your actions:
[OutputCache(Duration = 0, VaryByParam = "none")]
This will tell the browser to cache the page, but for 0 seconds. When the post reloads the page, you should see the desired results.
The answer is to make sure your form action is properly set. If you have used renderaction and not set the form controller and action manually then the action will be the current URL.
Try:
<% using (Html.BeginForm("ActionName", "ControllerName")) {%>
Instead of:
<% using (Html.BeginForm()) {%>