Table of Contents


Controller Attributes (Annotations)

Controller attributes (also called annotations) are special decorators that modify the behavior of controller classes and action methods. This section provides a complete reference for all available attributes in Magentrix.

Table of Contents

  1. Attributes Summary
  2. Attributes Overview
  3. Security Attributes
  4. Request Handling Attributes
  5. View & Rendering Attributes
  6. State Management Attributes
  7. Validation Attributes
  8. Localization Attributes
  9. Combining Attributes

Attributes Summary

AttributeSignature
Authorize[Authorize], [Authorize(Roles = "RoleName")]
AuthorizeAction[AuthorizeAction(Entity = "EntityName", Action = StandardAction.PermissionType)]
ValidateAntiForgeryToken   [ValidateAntiForgeryToken]
HttpPost[HttpPost]
AcceptVerbs[AcceptVerbs("GET", "POST")]
HandleExceptionsForJson[HandleExceptionsForJson]
MasterName[MasterName("LayoutName")]
AspxStyle[AspxStyle("EntityName")]
CompressOutput[CompressOutput(false)]
SerializeViewData[SerializeViewData]
SessionState[SessionState(SessionStateMode.Mode)]
CaptchaValidator[CaptchaValidator]
Localization[Localization(UICulture = "code", Culture = "code")]

Attributes Overview

Attributes provide a declarative way to control controller behavior without writing boilerplate code. They are applied to classes or methods using square brackets.

Basic Syntax

[AttributeName]
public ActionResponse MyAction()
{
    return View();
}

Multiple Attributes

[HttpPost]
[Authorize(Roles = "Administrator")]
[ValidateAntiForgeryToken]
public ActionResponse SecureAction()
{
    return View();
}

Attributes with Parameters

[AuthorizeAction(Entity = "Contact", Action = StandardAction.Edit)]
public ActionResponse EditContact(string id)
{
    return View();
}

Security Attributes

Security attributes control authentication and authorization for controller actions.

[Authorize]

Restrict access to authenticated users or users with specific roles.

Syntax:

[Authorize]
[Authorize(Roles = "RoleName")]
[Authorize(Roles = "Role1,Role2,Role3")]

Basic Authentication:

[Authorize]
public ActionResponse SecurePage()
{
    // Only authenticated users can access
    return View();
}

Role-Based Authorization:

[Authorize(Roles = "Administrator")]
public ActionResponse AdminPanel()
{
    // Only Administrators can access
    return View();
}

Multiple Roles (OR Logic):

[Authorize(Roles = "Sales Manager,Marketing Manager,Administrator")]
public ActionResponse ManagerDashboard()
{
    // Users with ANY of these roles can access
    return View();
}

💡 Note: Multiple roles use OR logic - users need only ONE of the specified roles.


[AuthorizeAction]

Check entity-level permissions before allowing access.

Syntax:

[AuthorizeAction(Entity = "EntityName", Action = StandardAction.PermissionType)]
[AuthorizeAction(Entity = "EntityName", Action = StandardAction.PermissionType, RecordIdParam = "paramName")]

Parameters:

  • Entity (required): Entity API name (e.g., "Contact", "Opportunity", "Account")
  • Action (required): Permission type from StandardAction enum
  • RecordIdParam (optional): Name of parameter containing record ID (defaults to "id")

StandardAction Values:

  • StandardAction.Read - List-level read permission
  • StandardAction.Detail - Record-level read permission
  • StandardAction.Create - Create permission
  • StandardAction.Edit - Edit permission
  • StandardAction.Delete - Delete permission

Examples:

// Check Read permission (list-level)
[AuthorizeAction(Entity = "Contact", Action = StandardAction.Read)]
public override ActionResponse Index()
{
    var contacts = Database.Query<Contact>().Limit(50).ToList();
    return View(contacts);
}

// Check Detail permission (record-level)
[AuthorizeAction(Entity = "Contact", Action = StandardAction.Detail)]
public ActionResponse Details(string id)
{
    var contact = Database.Retrieve<Contact>(id);
    return View(contact);
}

// Check Create permission
[AuthorizeAction(Entity = "Opportunity", Action = StandardAction.Create)]
public ActionResponse New()
{
    return View(new Opportunity());
}

[HttpPost]
[AuthorizeAction(Entity = "Opportunity", Action = StandardAction.Create)]
public ActionResponse New(Opportunity model)
{
    Database.Insert(model);
    return RedirectToAction("Index");
}

// Check Edit permission
[AuthorizeAction(Entity = "Opportunity", Action = StandardAction.Edit)]
public ActionResponse Edit(string id)
{
    var opp = Database.Retrieve<Opportunity>(id);
    return View(opp);
}

// Check Delete permission
[HttpPost]
[AuthorizeAction(Entity = "Account", Action = StandardAction.Delete)]
public ActionResponse Delete(string id)
{
    Database.Delete<Account>(id);
    return RedirectToAction("Index");
}

// Custom parameter name
[AuthorizeAction(Entity = "Contact", Action = StandardAction.Edit, RecordIdParam = "contactId")]
public ActionResponse EditContact(string contactId)
{
    var contact = Database.Retrieve<Contact>(contactId);
    return View(contact);
}

[ValidateAntiForgeryToken]

Protect against Cross-Site Request Forgery (CSRF) attacks.

Syntax:

[ValidateAntiForgeryToken]

Usage:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResponse Delete(string id)
{
    Database.Delete<Contact>(id);
    return RedirectToAction("Index");
}

How It Works:

  1. Magentrix automatically generates an anti-forgery token in forms
  2. The token is sent with the POST request
  3. The attribute validates that the token came from your portal
  4. Blocks requests from external sites attempting malicious actions
💡 Best Practice: Always use this attribute on POST actions that modify data, especially delete operations.

Request Handling Attributes

Control which HTTP methods can invoke your actions.

[HttpPost]

Restrict action to POST requests only.

Syntax:

[HttpPost]

Usage:

// GET: Display form
public ActionResponse Edit(string id)
{
    var contact = Database.Retrieve<Contact>(id);
    return View(contact);
}

// POST: Process form submission
[HttpPost]
public ActionResponse Edit(Contact model)
{
    if (ModelState.IsValid)
    {
        Database.Update(model);
        return RedirectToAction("Index");
    }
    
    return View(model);
}
💡 Note: Without [HttpPost], actions respond to GET requests by default.

[AcceptVerbs]

Allow action to respond to multiple HTTP verbs.

Syntax:

[AcceptVerbs("GET", "POST")]
[AcceptVerbs("GET", "POST", "PUT")]

Usage:

[AcceptVerbs("GET", "POST")]
public ActionResponse FlexibleAction()
{
    if (Request.HttpMethod == "POST")
    {
        // Handle POST
        return Json(new { success = true });
    }
    else
    {
        // Handle GET
        return View();
    }
}
Caution:
  • You cannot have method overloads with the same name if using [AcceptVerbs]
  • Generally prefer separate GET and POST methods for clarity

[HandleExceptionsForJson]

Ensure exceptions are returned as JSON instead of HTML error pages.

Syntax:

[HandleExceptionsForJson]

Usage:

[HandleExceptionsForJson]
public ActionResponse GetContact(string id)
{
    var contact = Database.Retrieve<Contact>(id);
    
    if (contact == null)
        throw new NotFoundException("Contact not found");
    
    return Json(contact, JsonRequestBehavior.AllowGet);
}

Without Attribute: If an exception occurs, the response is an HTML error page (not JSON-friendly for APIs).

With Attribute: Exceptions are automatically caught and returned as JSON:

{
    "success": false,
    "error": "Contact not found"
}
💡 Critical: Always use this attribute on actions that return JSON responses.

View & Rendering Attributes

Control how views are rendered and which master layouts are used.

[MasterName]

Specify which master layout/theme to use for the view.

Syntax:

[MasterName("LayoutName")]

Available Layouts:

  • "Site" - Default theme with header, footer, and navigation
  • "Setup" - Setup area theme with side menu
  • "Lookup" - Minimal theme for lookup dialogs (no header/footer)
  • "Public" - Public-facing theme for login/registration pages
  • "Blank" - No header or footer, just page content

Usage:

// Show page in Setup area with side menu
[MasterName("Setup")]
public ActionResponse AdminSettings()
{
    return View();
}

// Show page in lookup/popup style
[MasterName("Lookup")]
public ActionResponse SelectContact()
{
    var contacts = Database.Query<Contact>().ToList();
    return View(contacts);
}

// Public page without navigation
[MasterName("Public")]
public ActionResponse Login()
{
    return View();
}

// Blank page (useful for printing or embedding)
[MasterName("Blank")]
public ActionResponse PrintableInvoice(string id)
{
    var invoice = Database.Retrieve<Invoice>(id);
    return View(invoice);
}

Apply to Entire Controller:

[MasterName("Setup")]
public class AdminController : AspxController
{
    // All actions use "Setup" layout
    public override ActionResponse Index()
    {
        return View();
    }
    
    public ActionResponse Settings()
    {
        return View();
    }
}

[AspxStyle]

Apply tab styles from another entity to your Active Page.

Syntax:

[AspxStyle("EntityName")]

Usage:

[AspxStyle("Force.Force__Contact")]
public class MyCustomPageController : AspxController
{
    // This page will use Contact entity's tab styles
    public override ActionResponse Index()
    {
        return View();
    }
}

This makes your custom page visually consistent with standard entity pages.


[CompressOutput]

Control HTML output compression.

Syntax:

[CompressOutput(false)]

Default Behavior: By default, Magentrix compresses HTML output by removing unnecessary whitespace.

Disable Compression:

[CompressOutput(false)]
public ActionResponse ViewSource()
{
    // HTML will be rendered with original formatting/whitespace
    return View();
}

Use Cases:

  • Debugging HTML output
  • Viewing source code examples
  • Pages where whitespace matters

State Management Attributes

Control how data persists across requests.

[SerializeViewData]

Serialize model and DataBag across GET and POST requests.

Syntax:

[SerializeViewData]

How It Works:

Without [SerializeViewData]:

  • GET action passes model to view
  • View renders with model data
  • POST action receives only form fields
  • Original model data is lost

With [SerializeViewData]:

  • GET action passes model to view
  • Model and DataBag are serialized into ViewState
  • POST action receives both form data AND original model
  • You can compare old vs new values

Example:

// GET: Load contact for editing
[SerializeViewData]
public ActionResponse Edit(string id)
{
    var contact = Database.Retrieve<Contact>(id);
    
    // Store additional data
    DataBag.OriginalEmail = contact.Email;
    DataBag.EditStartTime = DateTime.Now;
    DataBag.PageTitle = $"Edit {contact.FirstName} {contact.LastName}";
    
    return View(contact);
}

// POST: Process changes
[HttpPost]
public ActionResponse Edit(Contact model)
{
    // DataBag values are still available!
    var originalEmail = DataBag.OriginalEmail;
    var editStartTime = (DateTime)DataBag.EditStartTime;
    var pageTitle = DataBag.PageTitle;
    
    if (ModelState.IsValid)
    {
        // Check if email changed
        if (originalEmail != model.Email)
            AspxPage.AddMessage($"Email changed from {originalEmail} to {model.Email}");
        
        // Calculate edit duration
        var duration = DateTime.Now - editStartTime;
        SystemInfo.Debug($"Edit took {duration.TotalSeconds} seconds");
        
        Database.Update(model);
        return RedirectToAction("Index");
    }
    
    // DataBag.PageTitle is still available for re-rendering
    return View(model);
}

Accessing Old and New Values:

[HttpPost]
public ActionResponse Edit(Opportunity model)
{
    if (ModelState.IsValid)
    {
        // Access new value (from form)
        var newStage = model.Stage;
        
        // Access old value (from ModelState)
        var oldStage = ModelState["Stage"]?.Value?.RawValue;
        
        if (oldStage?.ToString() != newStage)
            AspxPage.AddMessage($"Stage changed from {oldStage} to {newStage}");
        
        Database.Update(model);
        return RedirectToAction("Index");
    }
    
    return View(model);
}

💡 Use Cases:

  • Multi-step forms/wizards
  • Comparing old vs new values
  • Tracking user changes
  • Maintaining UI state across POST errors

[SessionState]

Control session state management for an action.

Syntax:

[SessionState(SessionStateMode.ReadOnly)]
[SessionState(SessionStateMode.Disabled)]

SessionStateMode Values:

  • ReadOnly - Session can be read but not modified
  • Disabled - Session is completely disabled
  • Required (default) - Full session access

Usage:

// Stateless API action for scalability
[SessionState(SessionStateMode.ReadOnly)]
[HandleExceptionsForJson]
public ActionResponse GetContacts()
{
    var contacts = Database.Query<Contact>().ToList();
    return Json(contacts, JsonRequestBehavior.AllowGet);
}

Benefits:

  • Improved performance for stateless operations
  • Better scalability for high-volume API endpoints
  • Reduced server memory usage

💡 Use for: REST API endpoints that don't need session state.


Validation Attributes

Control form validation behavior.

[CaptchaValidator]

Validate CAPTCHA input on form submission.

Syntax:

[CaptchaValidator]

Usage:

Active Page with CAPTCHA:

<aspx:AspxPage runat='server' title='Sign Up'>
    <body>
        <aspx:ViewSection runat='server' columns='one'>
            <aspx:InputField runat='server' value='{!Model.FirstName}' />
            <aspx:InputField runat='server' value='{!Model.LastName}' />
            <aspx:InputField runat='server' value='{!Model.Email}' />
            
            <!-- CAPTCHA field -->
            <aspx:FieldCaptcha runat='server' />
            
            <aspx:Button runat='server' text='Submit' />
        </aspx:ViewSection>
    </body>
</aspx:AspxPage>

Controller with Validation:

[HttpPost]
[CaptchaValidator]
public ActionResponse SignUp(Contact model)
{
    if (!ModelState.IsValid)
    {
        // CAPTCHA validation failed
        // ModelState will contain "ReCaptcha" error
        AspxPage.AddError("Please complete the CAPTCHA verification.");
        return View(model);
    }
    
    // CAPTCHA passed, process form
    Database.Insert(model);
    AspxPage.AddMessage("Thank you for signing up!");
    return RedirectToAction("Confirmation");
}

How It Works:

  1. User completes CAPTCHA on form
  2. [CaptchaValidator] validates the CAPTCHA response
  3. If invalid, adds error to ModelState["ReCaptcha"]
  4. Check ModelState.IsValid to see if CAPTCHA passed

💡 Use Cases:

  • Public registration forms
  • Contact forms
  • Any form accessible to non-authenticated users

Localization Attributes

Control language and culture settings.

[Localization]

Set UI culture and culture for the action response.

Syntax:

[Localization(UICulture = "culture-code", Culture = "culture-code")]

Parameters:

  • UICulture - Controls language for UI elements (buttons, labels, messages)
  • Culture - Controls formatting for dates, numbers, currency

Usage:

// French UI and culture
[Localization(UICulture = "fr-FR", Culture = "fr-FR")]
public ActionResponse FrenchPage()
{
    return View();
}

// Spanish UI and culture
[Localization(UICulture = "es-ES", Culture = "es-ES")]
public ActionResponse SpanishPage()
{
    return View();
}

// English UI with European number formatting
[Localization(UICulture = "en-US", Culture = "de-DE")]
public ActionResponse CustomFormat()
{
    return View();
}

Common Culture Codes:

  • en-US - English (United States)
  • en-GB - English (United Kingdom)
  • fr-FR - French (France)
  • es-ES - Spanish (Spain)
  • de-DE - German (Germany)
  • it-IT - Italian (Italy)
  • pt-BR - Portuguese (Brazil)
  • zh-CN - Chinese (Simplified)
  • ja-JP - Japanese

Combining Attributes

Attributes can be combined to create powerful, secure actions.

Example 1: Secure Form Submission

[HttpPost]
[Authorize(Roles = "Administrator")]
[ValidateAntiForgeryToken]
[AuthorizeAction(Entity = "Account", Action = StandardAction.Delete)]
public ActionResponse DeleteAccount(string id)
{
    // User must:
    // 1. Be authenticated
    // 2. Have Administrator role
    // 3. Submit valid anti-forgery token
    // 4. Have Delete permission on this specific Account
    
    Database.Delete(id);
    AspxPage.AddMessage("Account deleted successfully.");
    return RedirectToAction("Index");
}

Example 2: Stateful Edit with Security

[SerializeViewData]
[Authorize]
[AuthorizeAction(Entity = "Opportunity", Action = StandardAction.Edit)]
public ActionResponse Edit(string id)
{
    var opp = Database.Retrieve<Opportunity>(id);
    DataBag.OriginalStage = opp.Stage;
    DataBag.OriginalAmount = opp.Amount;
    return View(opp);
}

[HttpPost]
[ValidateAntiForgeryToken]
[AuthorizeAction(Entity = "Opportunity", Action = StandardAction.Edit)]
public ActionResponse Edit(Opportunity model)
{
    if (ModelState.IsValid)
    {
        // Compare changes
        var oldStage = DataBag.OriginalStage;
        var oldAmount = (decimal)DataBag.OriginalAmount;
        
        if (oldStage != model.Stage)
            AspxPage.AddMessage($"Stage changed from {oldStage} to {model.Stage}");
        
        Database.Update(model);
        return RedirectToAction("Index");
    }
    
    return View(model);
}

Example 3: JSON API with Security and Error Handling

[HandleExceptionsForJson]
[Authorize(Roles = "API User")]
[AuthorizeAction(Entity = "Contact", Action = StandardAction.Read)]
public ActionResponse GetContacts()
{
    var contacts = Database.Query<Contact>()
        .OrderBy(c => c.LastName)
        .ToList();
    
    return Json(contacts, JsonRequestBehavior.AllowGet);
}

[HttpPost]
[HandleExceptionsForJson]
[Authorize(Roles = "API User")]
[AuthorizeAction(Entity = "Contact", Action = StandardAction.Create)]
public ActionResponse CreateContact(Contact model)
{
    if (ModelState.IsValid)
    {
        Database.Insert(model);
        return Json(new { success = true, id = model.Id });
    }
    
    return Json(new { success = false, errors = ModelState }, 400);
}

Example 4: Public Form with CAPTCHA

[MasterName("Public")]
public ActionResponse ContactUs()
{
    return View(new ContactForm());
}

[HttpPost]
[MasterName("Public")]
[CaptchaValidator]
public ActionResponse ContactUs(ContactForm model)
{
    if (!ModelState.IsValid)
    {
        AspxPage.AddError("Please correct the errors below.");
        return View(model);
    }
    
    // Process contact form
    SendEmail(model);
    
    AspxPage.AddMessage("Thank you! We'll be in touch soon.");
    return RedirectToAction("ThankYou");
}

Example 5: Multi-Step Wizard

[SerializeViewData]
[Authorize]
public ActionResponse Step1()
{
    DataBag.WizardStep = 1;
    DataBag.TotalSteps = 3;
    return View(new OpportunityWizard());
}

[HttpPost]
[SerializeViewData]
[ValidateAntiForgeryToken]
public ActionResponse Step1(OpportunityWizard model)
{
    if (ModelState.IsValid)
    {
        DataBag.Step1Data = model;
        DataBag.WizardStep = 2;
        return View("Step2", new ProductSelection());
    }
    
    DataBag.WizardStep = 1;
    return View(model);
}

[HttpPost]
[SerializeViewData]
[ValidateAntiForgeryToken]
public ActionResponse Step2(ProductSelection model)
{
    if (ModelState.IsValid)
    {
        DataBag.Step2Data = model;
        DataBag.WizardStep = 3;
        return View("Step3", new ReviewData());
    }
    
    DataBag.WizardStep = 2;
    return View(model);
}

[HttpPost]
[ValidateAntiForgeryToken]
[AuthorizeAction(Entity = "Opportunity", Action = StandardAction.Create)]
public ActionResponse Step3Confirm()
{
    var step1 = (OpportunityWizard)DataBag.Step1Data;
    var step2 = (ProductSelection)DataBag.Step2Data;
    
    // Create opportunity from wizard data
    var opportunity = new Opportunity
    {
        Name = step1.Name,
        AccountId = step1.AccountId,
        Amount = step2.TotalAmount,
        CloseDate = step1.CloseDate
    };
    
    Database.Insert(opportunity);
    AspxPage.AddMessage("Opportunity created successfully!");
    return RedirectToAction("Index");
}

Attribute Quick Reference

Security Attributes

AttributePurpose
[Authorize]Require authentication
[Authorize(Roles = "...")]Require specific role(s)
[AuthorizeAction(...)]Check entity permissions
[ValidateAntiForgeryToken]Prevent CSRF attacks

Request Handling

AttributePurpose
[HttpPost]Accept POST requests only
[AcceptVerbs("GET", "POST")]Accept multiple HTTP verbs
[HandleExceptionsForJson]Return errors as JSON

View & Rendering

AttributePurpose
[MasterName("Layout")]Specify master layout
[AspxStyle("Entity")]Apply entity tab styles
[CompressOutput(false)]Disable HTML compression

State Management

AttributePurpose
[SerializeViewData]Persist model/DataBag across requests
[SessionState(...)]Control session state mode

Validation & Localization

AttributePurpose
[CaptchaValidator]Validate CAPTCHA input
[Localization(...)]Set culture/UI culture

Best Practices

1. Always Use Security Attributes for Sensitive Actions

Good:

[HttpPost]
[ValidateAntiForgeryToken]
[AuthorizeAction(Entity = "Account", Action = StandardAction.Delete)]
public ActionResponse Delete(string id)
{
    Database.Delete(id);
    return RedirectToAction("Index");
}

Bad:

[HttpPost]
public ActionResponse Delete(string id)
{
    Database.Delete(id);
    return RedirectToAction("Index");
}

2. Use [HandleExceptionsForJson] on All JSON Actions

Good:

[HandleExceptionsForJson]
public ActionResponse GetData()
{
    return Json(data, JsonRequestBehavior.AllowGet);
}

Bad:

public ActionResponse GetData()
{
    return Json(data, JsonRequestBehavior.AllowGet);
}

3. Match Attributes on GET and POST Actions

Good:

[AuthorizeAction(Entity = "Contact", Action = StandardAction.Edit)]
public ActionResponse Edit(string id)
{
    return View(Database.Retrieve<Contact>(id));
}

[HttpPost]
[ValidateAntiForgeryToken]
[AuthorizeAction(Entity = "Contact", Action = StandardAction.Edit)]
public ActionResponse Edit(Contact model)
{
    Database.Update(model);
    return RedirectToAction("Index");
}

4. Use [SerializeViewData] for Multi-Step Forms

[SerializeViewData]
public ActionResponse Step1()
{
    DataBag.StepData = "...";
    return View();
}

[HttpPost]
[SerializeViewData]
public ActionResponse Step1(Model data)
{
    // DataBag.StepData is still available
    return View("Step2");
}

5. Apply [MasterName] at Controller Level When Appropriate

[MasterName("Setup")]
public class AdminController : AspxController
{
    // All actions use Setup layout
}

Summary

Controller attributes provide powerful declarative control over:

Security - Authentication and authorization

Request Handling - HTTP method restrictions

View Rendering - Layout and compression control

State Management - Session and ViewState persistence

Validation - CAPTCHA and form validation

Localization - Culture and language settings

Attributes make code cleaner, more maintainable, and more secure by moving crosscutting concerns out of action logic.


Next Steps

You've now mastered all core controller concepts! Continue to:


💡 Master attributes to write cleaner, more secure controllers! Use them liberally to enforce security, manage state, and control behavior declaratively.