How create Ajax Helpers in MVC3 - ajax

i crate #Html.ActionLink helper here i check permissions of user. if yes i show this link otherwise not. now problem is with #Ajax.ActionLink can i make helper for Ajax.ActionLink? i make custom helpers to check permissions. it works fine with html.actionlink helper. How i check permissions in ajax actions ?
public static IHtmlString CustomActionLink(this HtmlHelper htmlHelper, int userId, string reqController, string reqAction, string linkText,int reqActionId = 0)
{
bool isAllowed = checkPermission(userId, reqController, reqAction, reqActionId);
if (isAllowed == false)
{
return MvcHtmlString.Empty;
}
return htmlHelper.ActionLink(linkText, reqAction, new { id =reqActionId });
}
i want to do this same check in Ajax Actions.

In ASP.NET MVC HTML helper methods are just extension methods to the existing HtmlHelper and AjaxHelper classes. Once you understand what an extension method is in .NET and how it works, it's not that difficult to apply this concept to the AjaxHelper class:
public static IHtmlString CustomAjaxActionLink(
this AjaxHelper ajaxHelper,
AjaxOptions ajaxOptions,
int userId,
string reqController,
string reqAction,
string linkText,
int reqActionId = 0
)
{
bool isAllowed = checkPermission(userId, reqController, reqAction, reqActionId);
if (!isAllowed)
{
return MvcHtmlString.Empty;
}
return ajaxHelper.ActionLink(
linkText,
reqAction,
new { id = reqActionId },
ajaxOptions
);
}
And inside your view simply use this custom helper (after bringing the namespace into which the containing class is declared into scope of course):
#Ajax.CustomAjaxActionLink(
new AjaxOptions { UpdateTargetId = "foo" },
123,
"SomeController",
"SomeAction",
"click me and get a surprise!",
456
)

How about using AuthorizeAttribute?
public class AuthorizeAdminAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if(!AppSecurity.Instance.IsUserInRoles(filterContext.HttpContext.User, AdminGroups))
{
HandleUnauthorizedRequest(filterContext);
}
base.OnAuthorization(filterContext);
}
}
and in your Controller you can use something like:
[AuthorizeAdmin]
public ActionResult Index()
{
return View();
}

Related

Why is the masterPath parameter in CreateView empty?

Writing a small proof of concept application and wondering why the masterPath parameter is empty:
in application_start:
ViewEngines.Engines.Add(new AlternateLocationViewEngine(
new string[] {
"~/Views/Shared/_Layout.cshtml", //Is this correct? Can/should i do that
"~/Views/Shared/{0}.cshtml",
"~/Plugins/Views/Shared/{0}.cshtml",
},
new string[] {
"~/Plugins/Views/{1}/{0}.cshtml",
"~/Plugins/{1}/{0}.chstml",
"~/Plugins/Views/Shared/{0}.cshtml"
}
));
public class AlternateLocationViewEngine : RazorViewEngine
{
public AlternateLocationViewEngine(string[] masterLocations, string[] viewLocations)
: base()
{
MasterLocationFormats = masterLocations;
ViewLocationFormats = viewLocations;
PartialViewLocationFormats = ViewLocationFormats;
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
if (string.IsNullOrEmpty(masterPath))
{
masterPath = MasterLocationFormats.ElementAt(0);
}
var nameSpace = controllerContext.Controller.GetType().Namespace;
return base.CreateView(controllerContext, viewPath.Replace("%1", nameSpace), masterPath.Replace("%1", nameSpace));
}
}
As you see i 'm forced to check if masterPath is empty in method CreateView(). Why is this? Am i missing something fundamental?
My dev environment: ASP.NET MVC3, Razor, .NET4
The masterPath will only have a value when creating a ViewResult with a masterName.
protected internal ViewResult View(string viewName, string masterName);
Internally, the RazorView handles null masterPaths in it's constructor.
// where layoutPath is the masterPath arg from the RazorViewEngine's CreateView
LayoutPath = layoutPath ?? String.Empty;
When rendering the view, the RazorView will set the OverridenLayoutPath to the masterPath (if supplied).
// An overriden master layout might have been specified when the ViewActionResult got returned.
// We need to hold on to it so that we can set it on the inner page once it has executed.
webViewPage.OverridenLayoutPath = LayoutPath;
You do not need to specify the _Layout as one of the MasterLocationFormats. Below is the default behavior for the RazorViewEngine.
MasterLocationFormats = new[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
You can checkout the source code for more inspiration.

Dynamically set the disabled html attribute for TextBoxFor HtmlHelper

I'm trying to dynamically set the disabled attribute for the TextBoxFor HtmlHelper
#Html.TextBoxFor(model => model.Street,
new
{
#class = "",
disabled = (Model.StageID==(int)MyEnum.Sth) ? "disabled" : ""
})
but even if there is disabled="" it is the same as disabled="disabled". How to get around of this ?
I have the same problem about month ago and I finished by using this extension method for it
public static class AttributesExtensions
{
public static RouteValueDictionary DisabledIf(
this object htmlAttributes,
bool disabled
)
{
var attributes = new RouteValueDictionary(htmlAttributes);
if (disabled)
{
attributes["disabled"] = "disabled";
}
return attributes;
}
}
And after that you can use it like this
#Html.TextBoxFor(
model => model.Street,
new { #class = "" }.DisabledIf(Model.StageID==(int)MyEnum.Sth)
)
EDIT (after Paul's comment):
The using of data-xxx html attributes may be mined by using the constructor of the System.Web.Routing.RouteValueDictionary class, since underscores will not be automatically converted to minus sign.
Use the method System.Web.Mvc.HtmlHelper.AnonymousObjectToHtmlAttributes instead: will solve this issue.
UPDATED CODE (Extension method body only)
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (disabled)
{
attributes["disabled"] = "disabled";
}
return attributes;
Using the extension method below produces similar results, but this is perhaps more fragile:
#Html.TextBoxFor(
model => model.Street,
new { #class = "form-control" }
).DisabledIf(Model.IsReadOnly)
Extension:
using System.Text.RegularExpressions;
using System.Web.Mvc;
namespace xxx.HtmlHelpers
{
public static class MvcHtmlStringExtensions
{
private static readonly Regex OpeningTagPattern;
static MvcHtmlStringExtensions()
{
OpeningTagPattern = new Regex("<[a-zA-Z]*");
}
public static MvcHtmlString DisabledIf(this MvcHtmlString controlHtml, bool isDisabled)
{
if (!isDisabled) return controlHtml;
return
new MvcHtmlString(OpeningTagPattern.Replace(controlHtml.ToString(),
x => string.Format("{0} disabled=\"disabled\"", x.Groups[0])));
}
}
}
Probably your stage Id is not getting set
#{
if(Model.StageID != null && Model.StageID > 0)
{
#Html.TextBoxFor(model => model.Street,
new
{
#class = "",
disabled = (Model.StageID==(int)MyEnum.Sth) ? "disabled" : ""
})
}else{
#Html.TextBoxFor(model => model.Street,
new
{
#class = ""
})
}
}
We actually just ran into the same problem. We ended up implementing an extension method with overloaded parameters, which takes in a boolean indicating whether or not we want the control disabled. We just add the "disabled" attribute when appropriate, and let the built-in HtmlHelper handle the heavy lifting.
Extension class and method:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
public static class OurHtmlHelpers
{
public const string DisabledAttribute = "disabled";
public static MvcHtmlString TextBoxFor<TModel, TProp>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProp>> expression,
object htmlAttributes,
bool canEdit)
{
var htmlAttributeDictionary = SetDisabledAttribute(htmlAttributes, canEdit);
return htmlHelper.TextBoxFor(expression, htmlAttributeDictionary);
}
private static RouteValueDictionary SetDisabledAttribute(object htmlAttributes, bool canEdit)
{
var htmlAttributeDictionary = new RouteValueDictionary(htmlAttributes);
if (!canEdit)
{
htmlAttributeDictionary.Add(DisabledAttribute, DisabledAttribute);
}
return htmlAttributeDictionary;
}
}
Then you just need to reference your new class and call #Html.TextBoxFor(m => m.SomeValue, new { #class = "someClass" }, <Your bool value>)
It's worth noting that you'd have to define these extensions for any of the TextBoxFor overloads you'd like to use, but it seems like a reasonable trade off. You can also utilize most of the same code for other HtmlHelpers you'd like to add the functionality to.

Routeconstraint with Ninject and dbcontext

I'm trying to build a constraint that checks against database. And I'm using Ninject, but for some reason it doesnt create a new instance of my repository when it fires.
global.asax.cs
// Content
routes.MapRoute(
"Content Language Route",
"{languageID}/List",
new { controller = "Content", action = "Index",
new { languageID = new LanguageRouteConstraint() },
new string[] { "MyProj.MVC.Controllers" }
);
.....
kernel.Bind<IContentRepository>().To<ContentRepository>();
Constraint
public class LanguageRouteConstraint : IRouteConstraint
{
#region IRouteConstraint Members
private readonly IContentRepository _contentRepository;
public LanguageRouteConstraint(IContentRepository contentRepository)
{
this._contentRepository = contentRepository;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.IncomingRequest)
{
string languageID = values["languageID"].ToString();
if (String.IsNullOrEmpty(languageID))
return false;
MyProj.MVC.Models.Language language = _contentRepository.GetLanguage(languageID);
return (language != null);
}
return false;
}
#endregion
}
Using Ninject for the repository works in the controller, but do I need to modify the route in gobal asa for it to make it work?
Solved it like this:
// Content
routes.MapRoute("Content Language Route",
"{languageID}/List",
new { controller = "Content", action = "Index",
new
{
languageID = new LanguageRouteConstraint(
DependencyResolver.Current.GetService<IContentRepository>())
},
new string[] { "MyProj.MVC.Controllers" }
);

Refactoring Switch statement in my Controller

I'm currently working on a MVC.NET 3 application; I recently attended a course by "Uncle Bob" Martin which has inspired me (shamed me?) into taking a hard look at my current development practice, particularly my refactoring habits.
So: a number of my routes conform to:
{controller}/{action}/{type}
Where type typically determines the type of ActionResult to be returned, e.g:
public class ExportController
{
public ActionResult Generate(String type, String parameters)
{
switch (type)
{
case "csv":
//do something
case "html":
//do something else
case "json":
//do yet another thing
}
}
}
Has anyone successfully applied the "replace switch with polymorhism" refactoring to code like this? Is this even a good idea? Would be great to hear your experiences with this kind of refactoring.
Thanks in advance!
The way I am looking at it, this controller action is screaming for a custom action result:
public class MyActionResult : ActionResult
{
public object Model { get; private set; }
public MyActionResult(object model)
{
if (model == null)
{
throw new ArgumentNullException("Haven't you heard of view models???");
}
Model = model;
}
public override void ExecuteResult(ControllerContext context)
{
// TODO: You could also use the context.HttpContext.Request.ContentType
// instead of this type route parameter
var typeValue = context.Controller.ValueProvider.GetValue("type");
var type = typeValue != null ? typeValue.AttemptedValue : null;
if (type == null)
{
throw new ArgumentNullException("Please specify a type");
}
var response = context.HttpContext.Response;
if (string.Equals("json", type, StringComparison.OrdinalIgnoreCase))
{
var serializer = new JavaScriptSerializer();
response.ContentType = "text/json";
response.Write(serializer.Serialize(Model));
}
else if (string.Equals("xml", type, StringComparison.OrdinalIgnoreCase))
{
var serializer = new XmlSerializer(Model.GetType());
response.ContentType = "text/xml";
serializer.Serialize(response.Output, Model);
}
else if (string.Equals("csv", type, StringComparison.OrdinalIgnoreCase))
{
// TODO:
}
else
{
throw new NotImplementedException(
string.Format(
"Sorry but \"{0}\" is not a supported. Try again later",
type
)
);
}
}
}
and then:
public ActionResult Generate(string parameters)
{
MyViewModel model = _repository.GetMeTheModel(parameters);
return new MyActionResult(model);
}
A controller should not care about how to serialize the data. That's not his responsibility. A controller shouldn't be doing any plumbing like this. He should focus on fetching domain models, mapping them to view models and passing those view models to view results.
If you wanted to "replace switch with polymorphism" in this case, you could create three overloaded Generate() ActionResult methods. Using custom model binding, make the Type parameter a strongly-typed enum called DataFormat (or whatever.) Then you'd have:
public ActionResult Generate(DataFormat.CSV, String parameters)
{
}
public ActionResult Generate(DataFormat.HTML, String parameters)
{
}
public ActionResult Generate(DataFormat.JSON, String parameters)
{
}
Once you get to this point, you can refactor further to get the repetition out of your Controller.

Cache based on Url Parameter in Asp.net MVC

i have defined a route culture/Controller/action/id... my controller contains following action..
[OutputCache(Duration=60*10)]
public ActionResult Index()
{*/do magic here/*}
is it possible to Cache contents based on Culture?
The localization complete guide presents an example of how to achieve this using the VaryByCustom parameter. In global.asax you would override the GetVaryByCustomString method:
public override string GetVaryByCustomString(HttpContext context, string value)
{
if (value == "lang")
{
return Thread.CurrentThread.CurrentUICulture.Name;
}
return base.GetVaryByCustomString(context, value);
}
and then:
[OutputCache(Duration = 60 * 10, VaryByParam = "none", VaryByCustom = "lang")]
public ActionResult Index()
{
/* do magic here */
...
}
Or if you want to rely solely on the culture route data parameter you could do this:
public override string GetVaryByCustomString(HttpContext context, string value)
{
if (value == "lang")
{
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
var culture = (string)routeData.Values["culture"];
if (!string.IsNullOrEmpty(culture))
{
return culture;
}
}
return base.GetVaryByCustomString(context, value);
}

Resources