In my MVC2 app that uses the Service - Repository pattern, how can I call a service method from the master page?
+--------------------------------------+
| Logo Welcome xyz|
+--------------------------------------+
| Home | Sales | Import | Admin (menu) |
+--------------------------------------+
In my menu I now have some pages that have restricted access by user role. I have an existing service method that can check if the current user can view a certain page or not:
IPageAccessService.CanAccess(int pageId, int roleId);
On the controller methods I can call this to check if the user can see the page or not:
public ActionResult Update(int id?)
{
if (!_pageAccessService.CanAccess(pageId, roleId))
{
return RedirectToAction("Index", "Home");
}
}
But I don't know how to call this method from my Site.Master so that when it creates the menu it does not show the menu item if the user does not have access (the menu is a simple unordered list):
<li>Admin
<ul>
<li>User Roles</li>
<li>Admin Reports</li>
</ul>
</li>
I'm guessing that it would need look something like this (have to check each page before adding to the list):
if (_pageAccessService.CanAccess(pageId, roleId)) <li>Admin
<ul>
if (_pageAccessService.CanAccess(pageId, roleId)) <li>User Roles</li>
if (_pageAccessService.CanAccess(pageId, roleId)) <li>Admin Reports</li>
</ul>
</li>
But before I can do that I need to know how to actually call a service method from the master.
EDIT:
I've adapted Darin's answer and have this:
public static class LinkExtensions
{
private static readonly IPageAccessRepository _repo = new PageAccessRepository();
private static readonly IPageAccessService _pageAccess = new PageAccessService(_repo);
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper, string linkText,
string url, string pageName
)
{
if (!_pageAccess.CanAccess(pageName))
{
return MvcHtmlString.Empty;
}
// The user can access the page => show the menu
var a = new TagBuilder("a");
a.Attributes["href"] = url;
a.SetInnerText(linkText);
return MvcHtmlString.Create(string.Format("<li>{0}</li>",a));
}
The problem is that I still need to call the service, so I need to be able to instantiate it. Because its a static class, my IoC container won't help here. So I still have to manually do create the service and repository. And it's still got the same problem as my original ugly workaround - manually creating a repository in a view.
You could write a custom HTML helper rendering the different items of this menu. Inside the helper based on the user roles you will decide whether to generate or not the given item. For example something among the lines:
public static class LinkExtensions
{
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string linkText,
string url,
string requiredRole
)
{
var a = new TagBuilder("a");
a.Attributes["href"] = url;
a.SetInnerText(linkText);
if (string.IsNullOrEmpty(requiredRole))
{
// No role required => show the menu item
return MvcHtmlString.Create(a.ToString());
}
var user = htmlHelper.ViewContext.HttpContext.User;
if (!user.IsInRole(requiredRole))
{
// A role is required but no user authenticated or user is not in role
// => show empty
return MvcHtmlString.Empty;
}
// The user is in role => show the menu
return MvcHtmlString.Create(a.ToString());
}
}
and inside the view:
<li>
<%= Html.MenuItem("Admin", Url.Content("~/Admin"), "admin") %>
<ul>
<li>
<%= Html.MenuItem("User Roles", Url.Content("~/Admin/Roles"), "userroles") %>
</li>
<li>
<%= Html.MenuItem("Admin Reports", Url.Content("~/Admin/AdminReports"), "admin") %>
</li>
</ul>
</li>
Another possibility is to use child actions and the Html.Action helper inside the master.
Anything that requires logic should not be in Views, including their helpers of course.
I'd suggest for this you add a new Controller Action, add your logic to the action, and return the username directly from the controller or if you want to display more stuff like login controls whatever you can use a view.
So, create LoginWelcomeMessage class with Username and IsLoggedIn properties for example. In the action set those based on the whatever checks you want, and send them to the view which shows/hides based on them.
In your masterpage, you execute #Html.RenderAction() to execute the action that brings the username and so on.
The same idea can be done for the entire menu. IT's up to you whether to have the whole header in one Action / View or have two different ones. If you have the same view then the model can have extra properties like CanViewSalesMenuItems, CanViewAdminMenuItems and so on, that are set from your action method, and used from its view to display/hide items.
The beauty is that the masterpage won't care, it just delegates the whole thing to the controller action and its view using RenderAction()
Related
I am trying to print the name of the user who executes my web app. It is written in MVC-Razor.
From the initial View, I would to execute the controller below:
[Authorize]
public ActionResult Check()
{
var check = new CheckAD();
var user = new User {Name = check.CheckSecurityWithAD()};
if (!string.IsNullOrEmpty(user.Name))
{
return View("Checked", user);
}
var errors = new ErrorsModel()
{
Messages = new List<string>(){"You don't have permission"}
};
return View("Error", errors);
}
This controller returns another view if the user is correctly authenticated:
#model UsersActivationWeb.Models.User
#{
ViewBag.Title = "Checked";
}
#{ <p> Logged come #Model.Name </p>};
How can I print the second view (I think it's a partial view) in the first one using the controller?
Thanks
Sounds to me like you need an Html.Action. This will run the controller code and display the view contents that are produced where you place the call.
Most likely you will need this overload, Html.Action(string actionName, string controllerName).
Assuming the controller is called CheckController. In your initial view call it like this
#Html.Action("Check","Check")
Since you don't want people navigating to the Check view you should give it a ChildActionOnly attribute so it looks like this
[Authorize]
[ChildActionOnly]
public ActionResult Check()
{
//rest of code
}
Finally you almost certainly don't want the layout contents to appear with the Checked view so change your Checked view to this
#model UsersActivationWeb.Models.User
#{
Layout = null;
}
#{ <p> Logged come #Model.Name </p>};
Since you are doing authorization logic in the Check action you might not need the Authorize attribute. I say that because with it a user not logged in will not get the error or their name. Maybe you want this though, I'd need to know more about your code to say for sure.
This way you will either get the name of the user or the errors as required.
I have this simple list of users in my model.
If we click one user, I would like to set that user as the chosen one and refresh the partial view.
My code looks like:
<div id="myPartialView">
#if (#Model.ChosenUser != null)
{
#Model.ChosenUser.UserName
}
<ul>
#foreach (var u in Model.Users)
{
<li>
<a href='#Url.Action("ChooseUser", "Controller", new { userId = u.UserId })'>#u.UserName</a>
</li>
}
</ul>
The controller method returns an Ok();
My current code redirects me to an empty page and I have to go back and refresh the page in order to see the model changes.
My question is, how can I refresh only this partial view after the razor action?
You will need to use Ajax.ActionLink here :
#foreach (var u in Model.Users)
{
<li>
#Ajax.ActionLink(u.UserName, // <-- Text to display
"Choose User", // <-- Action Name
"Controller", // <-- Controller Name
new AjaxOptions
{
UpdateTargetId="myPartialView", // <-- DOM element ID to update
InsertionMode = InsertionMode.Replace, // <-- Replace the content of DOM element
HttpMethod = "GET" // <-- HTTP method
})
</li>
}
The helper method would render the html needed to create the anchor tag element with the specified values, and you need to make sure that you have scripts added in the master layout for unobtrusive ajax.
For that you can look here, what scripts are needed to be pre-requisite:
How to use Ajax.ActionLink?
and your action method should be returning the model of the same type that your partial view expects with data populated in the model.
Please refer to the following post to learn in detail about Ajax Action Link:
http://www.c-sharpcorner.com/UploadFile/abhikumarvatsa/ajax-actionlink-and-html-actionlink-in-mvc/
I have a View displaying a list of items. I want to display an existing item when user select one to "edit", or display a new item when user clicks "create". This "single item" window will be called in many different places so I want combine the function "Edit" and "Create" into one view, which is not too bad. I understand I can do this by creating a view page on the same level of the caller, and when user clicks "submit" on this item window, the item controller do its job and then redirect back to the caller page (item list or other caller). But I want to try another approach, which is make this Item window as a pop up window, when user clicks on the "show modal" button, the caller page makes an ajax call to controller, passing item's data (or its id) as parameter, and the controller generate the MvcHtmlString dynamically, return to the caller, and caller display the popup window using the MvcHtmlString received. Everything seems to working, but the massive html code in my controller looks pretty messy.
So I'm wonder how to use HtmlHelper to generate MvcHtmlString in controller, for example, I have a object type "item", how can I do things like Html.LabelFor(item => item.Name), or Html.EditorFor(item => item.Name) in controller and get the MvcHtmlString?
You can extend your Htmlhelpers. If you want to just achieve edit/create button and you can change your viewmodel for condition or maybe you just want to use same model. You can use like this extension that i'm currently using on my views.
public static MvcHtmlString CustomActionLink(this HtmlHelper html, string action, string controller,
string displayText, bool isCreate)
{
if (isCreate)
{
var targetUrl = UrlHelper.GenerateUrl("Default", action, controller,
null, RouteTable.Routes, html.ViewContext.RequestContext, false);
var anchorBuilder = new TagBuilder("a");
anchorBuilder.MergeAttribute("href", targetUrl);
string classes = "btn btn-progress";
anchorBuilder.MergeAttribute("class", classes);
//Return as MVC string
anchorBuilder.InnerHtml = displayText;
return new MvcHtmlString(anchorBuilder.ToString(TagRenderMode.Normal));
}
else
{
var spanBuilder = new TagBuilder("span");
spanBuilder.MergeAttribute("class", "btn btn-progress");
spanBuilder.InnerHtml = displayText;
return new MvcHtmlString(spanBuilder.ToString(TagRenderMode.Normal));
}
}
I have a view that displays a list of comments. It does this via the DisplayTemplate. All I have to do is something like #Html.DisplayFor(x => x.BlogPost.PostComments) and all the comments render appropriately.
There is a form at the bottom of the page to add a new comment. This page utilizes progressive enhancement. So if javascript is disabled then the form submits like normal, adds the comment to the database, then redirects to the action that renders the blog post. However, if javascript is available then jQuery hijacks the form's submit and makes the post via ajax. Well because the comment markup is in a display template, I don't know how to return it from the action method so that jQuery can drop it on the page.
I know how to do this with partial views. I would just have the action method return the right partial view and jquery would append the response to the comment container on the page.
Before I go chopping out my display template in favor of a partial view, is there a straight forward way that I'm missing to send back a display template from the controller?
Here is my action method:
public ActionResult AddComment(PostComment postComment)
{
postComment.PostedDate = DateTime.Now;
postCommentRepo.AddPostComment(postComment);
postCommentRepo.SaveChanges();
if (Request.IsAjaxRequest())
return ???????
else
return RedirectToAction("BlogPost", new { Id = postComment.BlogPostID });
}
When the page loads it doesn't need to worry about it because it uses the templates in the standard way:
<div class="comments">
#Html.DisplayFor(x => x.BlogPost.BlogPostComments)
</div>
I just want to know how I might send a single comment that utilizes the display template back to jQuery.
You may try returning the partial HTML representing the newly posted comment:
if (Request.IsAjaxRequest())
{
return PartialView(
"~/Views/Shared/DisplayTemplates/Comment.cshtml",
postComment
);
}
and on the client side append this comment to the comments container:
$.post('#Url.Action("AddComment")', { ... }, function (result) {
$('#comments').append(result);
// or $('#comments').prepend(result); if you want it to appear on top
});
Does this question give you what you are looking for? Seems to indicate that you can call a HTML helper from an action.
Create a partial view /Shared/DisplayTemplate.cshtml with the following razor code:
#Html.DisplayFor(m => Model)
Then in your controller (or preferably in a base controller class) add a method along these lines:
protected PartialViewResult PartialViewFor(object model)
{
return PartialView("DisplayTemplate",model);
}
In the OP's case then:
public ActionResult AddComment(PostComment postComment)
{
postComment.PostedDate = DateTime.Now;
postCommentRepo.AddPostComment(postComment);
postCommentRepo.SaveChanges();
if (Request.IsAjaxRequest())
return PartialViewFor(postComment);
else
return RedirectToAction("BlogPost", new { Id = postComment.BlogPostID });
}
In an ASP.NET MVC application, I'm making logic for Admin to accept or reject new members. I'm showing a list of members and two buttons Accept and Reject, like this:
<% foreach (var mm in (ViewData["pendingmembers"] as List<MyMember>)) %>
<% { %>
<tr><td>Username:<%=mm.UserName %></td><td>
<tr><td>Firstname:<%=mm.FirstName %></td><td>
...etc...
<tr>
<td>
<% using (Html.BeginForm("AcceptPendingUser", "Admin"))
{ %>
<input type="submit" value="Accept" />
<% } %>
</td>
<td>
<% using (Html.BeginForm("RejectPendingUser", "Admin"))
{ %>
<input type="submit" value="Reject" />
<% } %>
</td>
</tr>
<% } %>
So, the list of pending member data is in a list of MyMember-objects. Each MyMember object will be printed out member and two buttons are setup for the admin to either accept or reject a pending member.
Then, in the controller I'm separating the handling of those two input fields/forms, like this:
public ActionResult AcceptPendingUser()
{
// TODO: Add code to save user into DB and send welcome email.
return RedirectToAction("Index");
}
public ActionResult RejectPendingUser()
{
// TODO: Add code to remove user from PendingUsers list and send rejection email.
return RedirectToAction("Index");
}
I would like to directly get the object next to the button the user pressed.
How can I send the MyMember object from the View to the controller?
Or how do I send perhaps a numeric index with button press? Maybe with a hidden field?
The simplest option would probably be a hidden input:
<input type="hidden" value="<%=mm.Key%>" name="key" id="key" />
(name accordingly; in each form)
The two controller would then take an argument called "key" (rename to suit). If you want to parse the object from multiple inputs, you'll need a ModelBinder. Of course, rather than 2*n forms, you might consider either query-string based urls, or use something like jQuery (or some other script helper) to submit the data without needing the forms (if script is available).
Instead of using an HTML button consider using an ActionLink and construct it to include the id of the member being approved. Another alternative would be to have a checkbox (whose value is the id of the member being approved) that the admin can select for each member to be approved and a similar one for reject and one each approve/reject buttons for the entire form.
Answering to myself and other mvc newbies:
I got it finally working with this code:
VIEW:
<%=Html.ActionLink(
"Jump",
"Jump",
new { name=(ViewData["Person"] as Person).Name,
person=ViewData["Person"]},
null) %>
CONTROLLER:
public ActionResult Index()
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC!";
Person p = new Person();
p.Name = "Barrack";
p.Age = 35;
ViewData["Person"] = p;
return View();
}
public ActionResult Jump(string name, Person person)
{
return View();
}
Debugging the app in the Jump method gives me nice "Barrack"-string for the name parameter, but Person parameter in null.
I also understand what the kind commenters tried to explain: it's easy to send simple data types like strings and ints to controller, but complex types such as my Person object needs something else.
Basically passing an int is enough for me. The hardest part here was figuring out the right way to set up ActionLink.
Cheers,
Pom