Showing posts with label mvc. Show all posts
Showing posts with label mvc. Show all posts

Sunday, September 23, 2012

Export to Excel with MVC

I’m sure you’ve all had to export to Excel at one point or another. I created a small wrapper around the EPPlus library for MVC. Basically my wrapper contains a few helpers for common formatting, an ActionResult, and a column definition. Here’s what the end result looks like in the simplest form:

Basically this exports all the columns and records in the SampleInfo collection. So if you want to specify the columns you want, you can do this:


The ExcelColumnDefinition.Create<> is kinda ugly, so you could create a small helper method for readability like this:


Now the above example looks like this:


So this is what the ExcelColumnDefinition looks like:


Here’s the actual ActionResult:


So that’s it. If you have any questions, please feel free to comment below.

You can browse or download the source on GitHub. The sample project has examples.

Shout it

kick it on DotNetKicks.com

Tuesday, July 31, 2012

Dynamically Generate Form with jQuery and ASP.NET MVC

Hopefully it’s obvious that you probably won’t need to do this very often, but when it’s needed, it’s nice to have available. I thought I’d share this little helper to dynamically create a form, append it to body, and submit it...without AJAX.

First I'll show you how I'm converting my view model to a parameter list and a query string. Basically I've created an extension method that loops through properties and checks if it's an IEnumerable or not. If it is, then I loop through the values of that property. Otherwise, I just add the value. This method does not work with complex types. Typically view models are flat, so this works well most of the time.

The ToQueryString method just calls the ToParameterList, adds a ? to the front and then just selects what it needs from the parameter list.

The jQuery is pretty straight forward. I get the body, create a form with an action, loop through my param list creating a hidden input for each param, append the form to the body, and then submit it.


As always, if you know of a different and/or better way, please let me know.

If you don't need to post, you can just use a get, you could do something like this:


You can download a sample project at GitHub.

Shout it

kick it on DotNetKicks.com

Wednesday, May 09, 2012

Testing for StructureMap Default Instances

Have you ever received the good ole “No Default Instance defined for PluginFamily”? error while using StructureMap? I thought I’d share a quick test you can write to catch this error before you deploy the bug. This test is particularly useful when you mostly depend on the default conventions.

In the example provided, I’ll be verifying that I can get a default instance of all my controllers. So I’ve setup 5 controllers and 2 have constructors with some type of dependency.

Something like this:

So now I can setup my test, which looks like this:


If you’re not familiar with TestCaseSource, I blogged about it a few weeks ago on the Headspring blog, so you can read about it there. The test queries the assembly containing HomeController for all types that are a subclass of Controller and are not abstract. After it gets the type, it tries to get an instance of it from the object factory. If it fails, the test fails with what specifically it couldn’t return and which controller couldn’t be instantiated.

Here’s what the test looks like when they all pass:

CropperCapture[7]

Here’s what the tests look like when the two with dependencies fails:

CropperCapture[8]

Here’s a detailed view of one of the error messages:

CropperCapture[9]

Notice it says IAuthenticationService and it failed on the AccountController, so we know exactly where to start looking. The only other thing I probably need to mention is the TestBase, which contains the StructureMap initialization. You could simply register your StructureMap registry file there. Here’s what the example looks like:

If anyone has any suggestions on how to improve the test or if StructureMap already has a test built-in that I’m not familiar with, please share.

kick it on DotNetKicks.com

Sunday, March 04, 2012

Create an ActionLink without Magic Strings

Updated: 3/7/2011

So it’s been a while. I have a new job working for Headspring in Austin, which is one of the reasons I haven’t blogged in a while. It’s tough moving and re-adjusting to a new area and job, but Austin is great and Headspring is terrific. I started blogging on the Headspring site too. I’m trying to alternate weeks for posting, so we’ll see how that goes. (also fyi, Headspring is hiring)

On to the good stuff, I started playing with an HtmlHelper extension to rid a project of magic strings for action links. It was definitely more tricky than I originally thought, but I have a first attempt ready to share. Here’s the code:

@(Html.ActionLinkFor<HomeController>(a=>a.Contact(Model), "Contact"
, new Dictionary<string, object>{{"style","color: #f90;background:#000"}})) <br />

@(
Html.ActionLinkFor<MainControllerController>(a=>a.Index(), "Main Home"))<br/>

@(Html.ActionLinkFor<HomeController>(a=>a.Index(), "Home")) <br />

@(Html.ActionLinkFor<HomeController>(a=>a.AnotherTest(new SampleModel{Id=5555
, Name =
"Just a test", Address = "123 Main"}), "Another Test"))<br/>

@(Html.ActionLinkFor<HomeController>(a=>a.TestYetAnother(new SampleModel{Id=12345678
, Name =
"Just another test yet"}), "Yet Another Test"))

The code above is what I wanted for the end result. The code below is what makes the code above work.

    public static class HtmlHelperExtensions
{
public static IHtmlString ActionLinkFor<TController>(this HtmlHelper htmlHelper
,
Expression<Func<TController, object>> expression,
string displayText) where TController : Controller
{
return ActionLinkFor(htmlHelper, expression, displayText, null);
}

public static IHtmlString ActionLinkFor<TController>(this HtmlHelper htmlHelper
,
Expression<Func<TController, object>> expression, string displayText
,
IDictionary<string, object> htmlAttributes)
where TController : Controller
{
var method = ((MethodCallExpression)expression.Body).Method;
var actionName = method.Name;
var parameters = method.GetParameters();
var arguments = ((MethodCallExpression)expression.Body).Arguments;

var routeValue = new RouteValueDictionary();
for (int i = 0; i < parameters.Length; i++)
{
if (arguments[i] as MemberExpression != null)
{
var memberExpression = (MemberExpression)arguments[i];
if (memberExpression.Expression as ConstantExpression == null)
AddRouteValuesForMemberExpression((
MemberExpression)arguments[i]
, parameters[i].Name, routeValue);

if (memberExpression.Expression as ConstantExpression != null)
AddRouteValuesForConstantExpression(((
MemberExpression)arguments[i])
, parameters[i].Name, routeValue);
}
if (arguments[i] as MemberInitExpression != null)
AddRouteValuesForMemberInitExpression((
MemberInitExpression)arguments[i]
, routeValue);
if (arguments[i] as MethodCallExpression != null)
AddRouteValuesForMethodCallExpression((
MethodCallExpression)arguments[i]
, parameters[i].Name, routeValue);
}

var controller = typeof(TController).Name;
var controllerName = controller.Remove(controller.LastIndexOf("Controller"));
return htmlHelper.ActionLink(displayText, actionName, controllerName, routeValue, htmlAttributes);
}

private static void AddRouteValuesForMethodCallExpression(MethodCallExpression methodCallExpression
,
string parameterName, RouteValueDictionary routeValue)
{
if (methodCallExpression.Object != null)
{
var value = ((PropertyInfo)((MemberExpression)methodCallExpression.Object).Member)
.GetValue(((
ConstantExpression)((MemberExpression)methodCallExpression.Object)
.Expression).Value,
null);
routeValue.Add(parameterName, value);
}
}

private static void AddRouteValuesForMemberExpression(MemberExpression memberExpression
,
string parameterName, IDictionary<string, object> routeValue)
{
object model;
if (((MemberExpression)memberExpression.Expression).Member is PropertyInfo)
model = ((
PropertyInfo)((MemberExpression)memberExpression.Expression).Member)
.GetValue(((
ConstantExpression)((MemberExpression)memberExpression.Expression)
.Expression).Value,
null);
else
model = ((FieldInfo)((MemberExpression)memberExpression.Expression).Member)
.GetValue(((
ConstantExpression)((MemberExpression)memberExpression.Expression)
.Expression).Value);

foreach (var p in model.GetType().GetProperties())
{
if (p.Name == memberExpression.Member.Name)
{
var value = p.GetValue(model, null);
if (value != null && !routeValue.ContainsKey(p.Name))
routeValue.Add(parameterName, value.ToString());
}
}
}

private static void AddRouteValuesForConstantExpression(MemberExpression memberExpression
,
string parameterName, IDictionary<string, object> routeValue)
{
var properties = ((PropertyInfo)memberExpression.Member)
.GetValue(((
ConstantExpression)memberExpression.Expression).Value, null)
.GetType()
.GetProperties();

foreach (var property in properties)
{
var value = property.GetValue(((PropertyInfo)memberExpression.Member)
.GetValue(((
ConstantExpression)memberExpression.Expression).Value, null), null);

if (value != null)
routeValue.Add(property.Name, value.ToString());
}

if (properties.Length == 0)
{
var value = ((PropertyInfo)memberExpression.Member)
.GetValue(((
ConstantExpression)memberExpression.Expression).Value, null);

if (value != null)
routeValue.Add(parameterName, value.ToString());
}
}

private static void AddRouteValuesForMemberInitExpression(MemberInitExpression memberInitExpression
,
IDictionary<string, object> routeValue)
{
foreach (var p in memberInitExpression.Bindings.Cast<MemberAssignment>())
{
if (p.Expression is ConstantExpression)
{
var value = ((ConstantExpression)p.Expression).Value;
var name = p.Member.Name;
if (value != null)
routeValue.Add(name, value.ToString());
}
if (p.Expression is UnaryExpression)
{
throw new NotImplementedException("Not able to handle UnaryExpressions yet.");
}
}
}
}

Basically the way this thing works is off the Expression<Func<TController, object>> parameter being passed into the ActionLinkFor method. From the expression, you can determine the route values and action name…by doing a lot of manipulation. So, if you would, try this helper out and let me know of any bugs or improvements that can be made.


Shout it

kick it on DotNetKicks.com

Wednesday, November 09, 2011

Refactoring MVC Routes

Some of my team members and myself are participating in the Houston AIR competition this weekend and we are building our project this week. Of course we’ll have to rebuild it from scratch on Saturday, but hopefully it’ll just be a bunch of re-typing and no surprises. Anyhow, one of my duties on the team is to configure MVC and the interfaces we’ll need and all that jazz. So when I went to setup the routes, I was surprised again at how flexible it all is to setup. Typically you can get by with the default route, but not in our case this time. Basically we wanted to have these routes:

/story/give
/story/{id}
/feature/{id}
/prayer/{id}
/{pagename}

By the way, our assigned charity is a church in Houston called the Household of Faith Church - South Acres. At first I had this: (which is TOTALLY RIDCULOUS, but I wanted to see them all)
routes.MapRoute(
"Home",
"",
new { controller = "Main", action = "Home" });

routes.MapRoute(
"GiveStory",
"give/story",
new { controller = "Main", action = "GiveStory" });

routes.MapRoute(
"Feature",
"feature/{id}",
new { controller = "Main", action = "Feature", id=UrlParameter.Optional });

routes.MapRoute(
"Prayer",
"Prayer/{id}",
new { controller = "Main", action = "Prayer", id = UrlParameter.Optional });

routes.MapRoute(
"Story",
"Story/{id}",
new { controller = "Main", action = "Story", id = UrlParameter.Optional });

routes.MapRoute(
"About",
"About",
new { controller = "Main", action = "About"});

routes.MapRoute(
"Directions",
"Directions",
new { controller = "Main", action = "Directions" });

routes.MapRoute(
"SiteMap",
"SiteMap",
new { controller = "Main", action = "SiteMap" });

routes.MapRoute(
"Help",
"Help",
new { controller = "Main", action = "Help" });
I typically setup my routes early on because I usually know what I want the URLs to be and it helps when naming my actions on the controllers. So obvioulsy we only have one controller because it’s not a large site and they all kinda go together, but what’s up with all the actions on the route configuration? Ridiculous right? So let’s get rid of all that mess like this:
routes.MapRoute(
"GiveStory",
"give/story",
new { controller = "Main", action = "GiveStory" });

routes.MapRoute(
"Feature",
"feature/{id}",
new { controller = "Main", action = "Feature", id=UrlParameter.Optional });

routes.MapRoute(
"Prayer",
"Prayer/{id}",
new { controller = "Main", action = "Prayer", id = UrlParameter.Optional });

routes.MapRoute(
"Story",
"Story/{id}",
new { controller = "Main", action = "Story", id = UrlParameter.Optional });

routes.MapRoute(
"Page",
"{viewname}",
new { controller = "Main", action = "GetStatic", viewname = "Home" });
Simple enough right? So I created an action called GetStatic that just accepts the view name for that page. The action looks like this:
public ViewResult GetStatic(string viewname)
{
return View(viewname);
}
Now let’s address the other redundant mess I have here…the {feature/prayer/story}/{id} route. Basically we’re going to do that and add a constraint to the route like this:
routes.MapRoute(
"GiveStory",
"give/story",
new { controller = "Main", action = "GiveStory" });

routes.MapRoute(
"WebSectionRoute",
"{WebType}/{Id}",
new { Controller = "Main", action = "GetWebSection", Id = UrlParameter.Optional }, new {WebType = "Story|Feature|Prayer"});

routes.MapRoute(
"Page",
"{viewname}",
new { controller = "Main", action = "GetStatic", viewname = "Home" });
So what this says is if the WebType contains story/feature/prayer then use this route otherwise keep on going. I can’t express enough the importance of the route order here. It’s an easy thing to miss and I’ve caught the missed order a few times for other developers and myself. Make sur eyou put them in the order they need to be in and not in some random order. Well that’s it. Route configuration refactored and now it doesn’t have any redundant routes.

Thanks for reading!

kick it on DotNetKicks.com

Sunday, May 22, 2011

Contact Form Revisited with ASP.NET MVC 3, jQuery Validator, & the jQuery Form Plugin




I recently added a partial contact view to my MVC3 project and thought I’d share since I did basically this same post over a year ago with the original MVC. I stopped using the Castle Validation because I’ve found that the MVC3 stuff is working for me now. I also am not using fluentHtml anymore because MVC3 uses that style now.

Okay…let’s get started.

We’ll start with the view model class like the last post. By the way, if you’re not familiar with the way I setup my MVC projects, see this post. (If you read my first Contact Form post, you’re probably experiencing déjà vu).

public class ContactView
{
[Required]
public string Name { get; set; }
[Required, ValidateEmail(ErrorMessage = "Valid email is required.")]
public string Email { get; set; }
[Required]
public string Subject { get; set; }
[Required]
public string Message { get; set; }
}

The Required attribute is exactly what it seems like and it’s part of the System.ComponentModel.DataAnnotations. The default error message is “The [propertyname] is required.”. If you want to reset it, you can do this: [Required(ErrorMessage = “Whatever I want it to be”)].

The ValidateEmail is a custom validation attribute. I didn’t like the looks of having a RegularExpressionAttribute defined there and since email is such a common thing to validate, I made this one:

public class ValidateEmailAttribute : RegularExpressionAttribute
{
public ValidateEmailAttribute(): base(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
{
}
}

It just inherits from the RegularExpressionAttribute and passes in the regex to validate an email address. Apparently there is much discussion on how to validate an email address, which I believe is why Microsoft didn’t provide this to us.

So let’s look at the View/XHTML Markup.

@model Sample.Core.UI.Model.ContactView
@using Sample.Core.UI.Helpers
@using (Html.BeginForm("Contact", "Home", FormMethod.Post, new { id = "contactform" }))
{
<fieldset>
<legend>Contact Us</legend>
    <p>
@Html.LabelFor(f => f.Name, "Your Name")<br />
@Html.TextBoxFor(f => f.Name, new { style = "width: 200px", @class="required", accesskey="n" })
</p>
<p>
@Html.LabelFor(f => f.Email, "Your Email")<br />
@Html.TextBoxFor(f => f.Email, new { style = "width: 200px", accesskey = "e" })
</p>
<p>
@Html.LabelFor(f => f.Subject, "Subject")<br />
@Html.TextBoxFor(f => f.Subject, new { style = "width: 200px", accesskey = "p" })
</p>
    <p>
@Html.LabelFor(f => f.Message, "Message")<br />
@Html.TextAreaFor(f => f.Message, new { style = "width: 350px", rows = "4", accesskey = "c" })
</p>
@Html.AntiForgeryToken()
<input type="submit" id="bContact" name="bContact" value="Send Message" accesskey="s" /> 
@Html.DivSuccessMessage("Message sent successfully", "contactsuccess") 
@Html.ValidationSummary("", new { id = "contacterror" })
<noscript><br /><br /><div class="tip">Our contact form may look and act funny because you have JavaScript disabled. For a better experience on thissample.com, please enable JavaScript.</div></noscript>
</fieldset>
}

You can see at the top of the page I specify my view model. I ‘m also referencing a helper namespace for my DivSuccessMessage extension. Basically all it does is checks the ModelState for errors and for ViewData[“success”] not being null and displays the message specified. After that it’s basically a plain ole HTML form.

Okay, now we have our form built with our view model. Below the end of the XHTML above, I have the following jQuery code.

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.8/jquery.validate.min.js"></script>
<script type="text/javascript" src="http://www.malsup.com/jquery/form/jquery.form.js"></script><script language="javascript" type="text/javascript">
$(document).ready(function () {
var formoptions = { beforeSubmit: function (formData, jqForm, options) {
$("#bContact").attr('value', 'sending...');
$("#bContact").attr('disabled', 'disabled');
}, success: function (data) {
if (data.status == "Success") {
$("#contacterror").hide();
showMessage("#contactsuccess", data.message);
$('.valid').removeClass('valid');
validator.resetForm();
}
else {
$("#contactsuccess").hide();
showMessage("#contacterror", data.message);
}
$("#bContact").attr('value', 'Send Message');
$("#bContact").removeAttr('disabled');
}, dataType: "json"
};jQuery.validator.messages.required = "";
var validator = $("#contactform").validate({
submitHandler: function (form) {
$(form).ajaxSubmit(formoptions);
},
invalidHandler: function (e, validator) {
var errors = validator.numberOfInvalids();
if (errors) {
var message = errors == 1 ? 'You missed 1 field. It has been highlighted.' : 'You missed ' + errors + ' fields.  They have been highlighted.';
showMessage("#contacterror", message);
$("#contactsuccess").hide();
} else {
$("#contacterror").hide();
}
},
messages: { Email: { email: ""} },
rules: {
Subject: "required",
Message: "required",
Email: { required: true, email: true }
},
errorClass: "invalid",
validClass: "valid",
errorContainer: "#contacterror"
});function showMessage(id, message) {
$(id).html(message);
$(id).show();
}
});
</script>

Let’s break this down. First section the formOptions:

var formoptions = { beforeSubmit: function (formData, jqForm, options) {
$("#bContact").attr('value', 'sending...');
$("#bContact").attr('disabled', 'disabled');
}, success: function (data) {
if (data.status == "Success") {
$("#contacterror").hide();
showMessage("#contactsuccess", data.message);
$('.valid').removeClass('valid');
validator.resetForm();
}
else {
$("#contactsuccess").hide();
showMessage("#contacterror", data.message);
}
$("#bContact").attr('value', 'Send Message');
$("#bContact").removeAttr('disabled');
}, dataType: "json"
};

This code is used to define all my options for the jQuery.Form plugin. What it says is this:

  • beforeSubmit – Change the button to say “sending…” and disable it
  • on success – if the status = “Success” then hide the contacterror div, show the success message, manually remove the valid class from my inputs, and reset the form. Otherwise, hide the success message and show the error message with the message received. Regardless, re-enable my button and make it say “Send Message”. (Note: I shouldn’t have to manually remove the valid class, but the resetForm wouldn’t do it for me like it’s supposed to do.)
  • dataType – json received from my action

The validation section:

jQuery.validator.messages.required = "";
var validator = $("#contactform").validate({
submitHandler: function (form) {
$(form).ajaxSubmit(formoptions);
},
invalidHandler: function (e, validator) {
var errors = validator.numberOfInvalids();
if (errors) {
var message = errors == 1 ? 'You missed 1 field. It has been highlighted.' : 'You missed ' + errors + ' fields.  They have been highlighted.';
showMessage("#contacterror", message);
$("#contactsuccess").hide();
} else {
$("#contacterror").hide();
}
},
messages: { Email: { email: ""} },
rules: {
Subject: "required",
Message: "required",
Email: { required: true, email: true }
},
errorClass: "invalid",
validClass: "valid",
errorContainer: "#contacterror"
});

This code defines my validation for the contact form. It says this:

  • Set all messages for required fields to empty by default
  • submitHandler – on submit do this, which it calls the ajaxSubmit contained in the jQuery.Form plugin with our options specified above.
  • invalidHandler – if the form isn’t valid, get the number of errors and show the error message. Otherwise, hide the error message.
  • messages – Defines what the message should be for email, which is empty. I would’ve had to specify the required messages too had I not set them to empty first. Also note that the Email: has to match the ID of one of  your inputs.
    • Example: <input type=”text” id=”whateverid”/> so the messages would looks like this:

      messages: {whateverid: {required: “some message”}}

  • rules – Defines the rules for each input. Phone, Comments are required and Email has required and email. Notice name is required, but not specified here. It’s because I added the required class to the Name input in the XHTML instead of specifying down here so you could see you have options.
  • errorClass – Specifies my style class for when the input is invalid.
  • validClass – Specifies my style class for when the input is valid.
  • errorContainer – Specifies the div I want to show my error messages in.

Final section:

function showMessage(id, message) {
$(id).html(message);
$(id).show();
}

This just finds the container sets the html and shows it.

Okay, so finally here’s what the controller looks like that we mentioned in the @Html.BeginForm() section above.

[HttpPost, ValidateAntiForgeryToken, ValidateInput(true)]
public ActionResult Contact(ContactView view)
{
if (!ModelState.IsValid)
{
if (Request.IsAjaxRequest())
return Json(new { status = "error", message = "All fields are required." });

return View(view);
}    try
{
var notificationService = DI.EmailNotificationService(new EmailNotification(view));
notificationService.Notify();
}
catch (NotificationException)
{
ModelState.AddModelError("notifyerror", "Could not connect to mail server.");
}    if (Request.IsAjaxRequest())
return ModelState.IsValid ? Json(new {status = "Success", message = "Message sent successfully."}) : Json(new {status = "error", message = "Could not connect to mail server."});    return ModelState.IsValid ? Success(view) : View(view);
}

So this action accepts HttpPost, must have a valid Anti-Forgery Token, and it validates the input. First thing it does is verifies the modelstate is valid. The reason for this is that some people run their browser with JavaScript disabled. So we have to account for that in our code and make sure that we are validating on the client-side and on the server-side. So if the ModelState is invalid, we have to check to see if it’s an AJAX request. if it is, we return a Json result with the status of error and a message stating all fields are required. If it’s not an AJAX request, we simply return the view.

If all is valid, we continue and go ahead and send the notification. If the notification bombs, we add an error to the modelstate and then recheck and act accordingly. If you want to know what the notification service looks like, please refer to the first post because it’s all the exact same.

So, this method of coding will work when JavaScript is enabled and disabled and all the data will be validated regardless as well.

Here’s what the screen looks like after just hitting Send Message:

image

Here’s what it looks like after all the fields are valid right before I hit Send Message:

image

After message sent:

image

Please let me know if you have any questions.

Download Demo

Thanks for reading!

Shout it

kick it on DotNetKicks.com

Friday, April 15, 2011

ASP.NET MVC 3 Sample Project Launched




Okay, after popular demand of I think 0 people, I’ve published my old demo MVC 1 project as an MVC 3 project. If you haven’t read any of the posts on the last demo project, check out this post. This project completely separates UI & C# code, so you only have 2 projects (Core & UI…not counting the Unit Tests project).

Basically, all I did was create a new MVC 3 project using Razor with Microsoft’s default template and deleted everything except the following:

  • Views folder
  • Root Default.aspx
  • global.asax (I did delete the global.asax.cs)
  • both web.configs

I referenced my Core, setup my views that match my Core project, and inherited from my global.cs in the global.asax. That was it!

You can download it via zip here: http://code.google.com/p/derans/downloads/list (the file is called DemoPhotographySite_v3.zip)

I feel like this project will act as a great stepping stone to understanding the Code Camp server, which is much more complex.

The sample project was built with the following tools:

If you downloaded the old one, you’ll notice that I removed the following 3rd parties:

The primary reason I removed these three parties is because MVC 3 and Razor are good enough so you don’t need the 3rd party tools.

The best practices I mentioned above come straight from experience and the following people/resources:

You can see the exact same Core code in use at sweetandhappy.com. If you see any improvements that can be made or you’d just like to comment, please do so!

Also, please note that I basically am even re-posting the exact same blog post I did over a year ago with my original MVC demo project. I hope you don’t mind, but it’s late and I have to work tomorrow :)

Thanks for reading!

Shout it

kick it on DotNetKicks.com

Sunday, March 20, 2011

Simplify Authorization with an Attribute




There are about 10 bajillion ways to figure out if a user is authorized to call a method or see a page or whatever else. This particular post is about how to create an attribute to see if a user is authorized to do a particular action. I’ll give an example using MVC and WebForms.

For MVC, you’d create a filter attribute and override the OnActionExecuting method like this:

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class AllowAttribute : ActionFilterAttribute
{
private readonly string[] _allowedRoles;

public AllowAttribute(params string[] allowedRoles)
{
_allowedRoles = allowedRoles;
}

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//if (authorized)
//return

filterContext.Controller.ViewData.ModelState.AddModelError("AccessDenied", "Access denied");
throw new AccessDeniedException();
}
}

The commented out part is where you’d actually call your authorization service or however you want to go about authorizing a user. I’m accepting the roles allowed for the particular method in the constructor. Here’s the AccessDeniedException:

    public class AccessDeniedException : BaseHttpException
{
public AccessDeniedException() : base((int)HttpStatusCode.Unauthorized, "User not authorized.") { }
}

public class BaseHttpException : HttpException
{
public BaseHttpException(int httpCode, string message) : base(httpCode, message) { }
}

So on the controller action, it’d look something like this:

        [Allow(Roles.Administrator, Roles.OtherSampleRole)]
public ActionResult SampleAction()
{
return RedirectToAction("SampleAction");
}

Since I hate risking mistyping something, I’ll typically setup an enum or a class with consts…something like this:

    public class Roles
{
public const string Administrator = "Administrator";
public const string OtherSampleRole = "OtherSampleRole";
}

Here’s the WebForm version:

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class AllowAttribute : Attribute
{
public AllowAttribute(params string[] allowedRoles)
{
//if (authorized)
//return

throw new AccessDeniedException();
}
}

This one is used like this:

    [Allow(Roles.Administrator, Roles.OtherSampleRole)]
public class SamplePage : System.Web.UI.Page
{
/*...*/
}

The WebForm version is based on page instead of by action like on the MVC sample. Pretty simple stuff…hope this helps.

Also, you'll need to add the 401 code to the custom errors section and redirect to an access denied page.

Thanks for reading!

Shout it

kick it on DotNetKicks.com

Related Posts Plugin for WordPress, Blogger...