Controller Responses
Controllers communicate with users by returning different types of responses. This section provides a complete reference for all response types available in Magentrix, from rendering pages to generating files, streaming JSON data, and redirecting users.
Table of Contents
- Response Types Summary
- Response Types Overview
- View Responses
- PartialView Response
- Predefined Views
- Redirect Responses
- RedirectToAction
- Redirecting to Active Pages
- File Responses
- StorageFile Response
- PDF Response
- Data Responses
- Special Responses
- Response Type Decision Guide
- Practical Examples
- Best Practices
- Common Pitfalls
- Summary
- Next Steps
- Quick Reference
Response Types Summary
Response Types Overview
Every controller action must return an ActionResponse object. The type of response determines what the user sees or receives.
Response Type Comparison
| Response Type | Purpose | Common Use Cases |
|---|
| View() | Render Active Page | Display forms, dashboards, detail pages |
| PartialView() | Render page fragment | Widgets, reusable components |
| Redirect() | Navigate to URL | Post-form submission, external links |
| RedirectToAction() | Navigate to action | Navigate between controller actions |
| Json() | Return JSON data | REST APIs, AJAX responses |
| File() | Stream file download | CSV exports, binary files |
| StorageFile() | Stream from cloud | Download files from Magentrix Storage |
| Pdf() | Generate PDF | Dynamic reports, invoices |
| Content() | Return text | Plain text, HTML, XML, CSS, JavaScript |
| UnauthorizedAccessResponse() | Access denied | Security violations |
| PageNotFound() | 404 error | Missing resources |
| RedirectToError() | Error page | Exception handling |
| IrisData() | Iris JSON payload | Vue 3 (Iris) endpoints returning model + permissions + metadata |
View Responses
View responses render Active Pages to display HTML user interfaces. They are the most common response type for Page Controllers.
Basic View Response
public override ActionResponse Index()
{
return View();
}
This renders the Active Page associated with the controller.
View with Model
Pass data to the Active Page using a model:
public ActionResponse Details(string id)
{
Contact contact = Database.Retrieve(id);
return View(contact);
}
Accessing in Active Page:
<aspx:AspxPage runat='server' title='Contact Details'>
<body>
<h1>{!Model.FirstName} {!Model.LastName}</h1>
<p>Email: {!Model.Email}</p>
</body>
</aspx:AspxPage>
View with Collection Model
public override ActionResponse Index()
{
List<Account> accounts = Database.Query<Account>()
.OrderBy(a => a.Name)
.ToList();
return View(accounts);
}
Displaying in Active Page:
<aspx:Repeater runat='server' value='{!Model}' var='account'>
<body>
<table class='table'>
<thead>
<tr>
<th>Account Name</th>
<th>Type</th>
<th>Industry</th>
</tr>
</thead>
<tbody>
<tr>
<td><aspx:Field runat='server' value='{!account.Name}'/></td>
<td><aspx:Field runat='server' value='{!account.Type}'/></td>
<td><aspx:Field runat='server' value='{!account.Industry}'/></td>
</tr>
</tbody>
</table>
</body>
</aspx:Repeater>
View with Specific Active Page
Render a different Active Page than the default:
public ActionResponse Confirm()
{
return View(ActivePages.ConfirmationPage);
}
View with Active Page and Model
public ActionResponse ShowResults()
{
var results = Database.Query<Opportunity>()
.Where(o => o.Stage == "Closed Won")
.ToList();
return View(ActivePages.ResultsPage, results);
}
Available View() Overloads
// Uses default Active Page for the controller
View()
// Uses default Active Page with model
View(object model)
// Uses specific Active Page
View(ActivePage activePage)
// Uses specific Active Page with model
View(ActivePage activePage, object model)
PartialView Response
PartialView renders page fragments that can be embedded within other Active Pages or used as widgets.
Basic PartialView
public ActionResponse MyWidget()
{
return PartialView();
}
PartialView with Model
public ActionResponse OpportunitySummary(string accountId)
{
var opportunities = Database.Query<Opportunity>()
.Where(o => o.AccountId == accountId)
.ToList();
return PartialView(opportunities);
}
Using PartialView in Active Pages
In the parent Active Page:
<aspx:AspxPage Id='ParentPage' runat='server' title='Account Dashboard'>
<body>
<h1>Account Dashboard</h1>
<!-- Embed the partial view as a widget -->
<aspx:PartialView area='aspx' action='opportunitySummary'
controller='dashboard'
accountId='{!Model.Id}' />
</body>
</aspx:AspxPage>
Practical PartialView Examples
Example 1: Reusable Contact Card Widget
public class WidgetsController : AspxController
{
public ActionResponse ContactCard(string contactId)
{
Contact contact = Database.Retrieve(contactId);
return PartialView(contact);
}
}
Example 2: Real-Time Statistics Widget
public ActionResponse LiveStats()
{
var stats = new
{
ActiveUsers = Database.Query<User>().Where(u => u.IsActive).Count(),
OpenOpportunities = Database.Query<Opportunity>().Where(o => !o.IsClosed).Count(),
TodayRevenue = CalculateTodayRevenue()
};
return PartialView(stats);
}
Predefined Views
Magentrix provides several predefined views for common scenarios.
PageNotFound
Display a user-friendly 404 error:
public ActionResponse ViewResource(string id)
{
var resource = Database.Query<Resource>()
.Where(r => r.Id == id)
.FirstOrDefault();
if (resource == null)
{
return PageNotFound();
}
return View(resource);
}
RecordDeleted
Show message when a record has been deleted or is no longer accessible:
public ActionResponse Details(string id)
{
Contact contact = Database.Query<Contact>()
.Where(c => c.Id == id)
.FirstOrDefault();
if (contact == null)
{
return RecordDeleted();
}
return View(contact);
}
UnderMaintenance
Display maintenance page:
public ActionResponse Index()
{
bool isMaintenanceMode = CheckMaintenanceMode();
if (isMaintenanceMode)
{
return UnderMaintenance();
}
return View();
}
Error View with Custom Message
public ActionResponse ProcessData()
{
try
{
// Processing logic
return View();
}
catch (Exception ex)
{
return RedirectToError("An error occurred while processing your request. Please try again.");
}
}
Redirect Responses
Redirect responses navigate users to different URLs or actions.
Basic Redirect
Redirect to any URL:
public ActionResponse GoHome()
{
return Redirect("~/home/index");
}
The ~ symbol resolves to the application root.
Redirect to External URL
public ActionResponse GoToExternal()
{
return Redirect("https://www.example.com");
}
Permanent Redirect (301)
Use for permanently moved resources:
public ActionResponse OldPage()
{
return RedirectPermanent("/home/newpage");
}
This sends HTTP status code 301, which tells search engines the resource has permanently moved.
RedirectToAction
Redirect to another action in the same or different controller.
Redirect to Action in Same Controller
[HttpPost]
public ActionResponse Create(Contact model)
{
if (ModelState.IsValid)
{
Database.Insert(model);
return RedirectToAction("Index");
}
return View(model);
}
Redirect to Action with Parameters
[HttpPost]
public ActionResponse Create(Contact model)
{
if (ModelState.IsValid)
{
Database.Insert(model);
// Redirect to Details action with the new record's ID
return RedirectToAction("Details", new { id = model.Id });
}
return View(model);
}
Redirect to Action in Different Controller
public ActionResponse ProcessComplete()
{
return RedirectToAction("Edit", "Contact", new { id = "003R000000haXk3IAE" });
}
Parameters:
"Edit" - Action name"Contact" - Controller namenew { id = "..." } - Route values (parameters)
Permanent Redirect to Action
public ActionResponse OldAction()
{
return RedirectToActionPermanent("NewAction", "NewController");
}
Available RedirectToAction() Overloads
// Redirect to action in same controller
RedirectToAction(string actionName)
// Redirect to action with parameters
RedirectToAction(string actionName, object routeValues)
// Redirect to action in different controller
RedirectToAction(string actionName, string controllerName, object routeValues)
// Permanent redirect versions
RedirectToActionPermanent(string actionName, object routeValues)
RedirectToActionPermanent(string actionName, string controllerName, object routeValues)
Redirecting to Active Pages
Use the ActivePages helper to redirect to specific Active Pages with type safety:
public ActionResponse GoToContactPage()
{
return Redirect(ActivePages.ContactManager);
}
Redirect to Active Page with Parameters
public ActionResponse ViewContact(string id)
{
return Redirect(ActivePages.ContactDetails, new { id = id });
}
This approach is recommended because:
- Maintains system dependencies
- Provides compile-time checking
- System notifies you if Active Page is referenced elsewhere
File Responses
File responses stream binary files to the user's browser for download.
Basic File Response
public ActionResponse DownloadFile()
{
byte[] fileBytes = GetFileContent();
return File(fileBytes, "application/pdf", "document.pdf");
}
Parameters:
fileBytes - The file content as byte array"application/pdf" - MIME content type"document.pdf" - Suggested filename for download
Common MIME Types
| File Type | MIME Type |
|---|
| PDF | application/pdf |
| Word (.docx) | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
| Excel (.xlsx) | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
| CSV | text/csv |
| Plain Text | text/plain |
| JSON | application/json |
| XML | application/xml |
| ZIP | application/zip |
| PNG | image/png |
| JPEG | image/jpeg |
CSV Export Example
public ActionResponse ExportContactsCsv()
{
var contacts = Database.Query<Contact>().ToList();
var csv = new StringBuilder();
csv.AppendLine("First Name,Last Name,Email,Phone");
foreach (var contact in contacts)
{
csv.AppendLine($"{contact.FirstName},{contact.LastName},{contact.Email},{contact.Phone}");
}
byte[] fileBytes = Encoding.UTF8.GetBytes(csv.ToString());
return File(fileBytes, "text/csv", "contacts.csv");
}
Excel Export Example
public ActionResponse ExportAccountsExcel()
{
var accounts = Database.Query<Account>().ToList();
// Use external libraries generate Excel
byte[] excelBytes = GenerateExcelFile(accounts);
return File(excelBytes,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"accounts.xlsx");
}
Reading Note and Attachment Files
public ActionResponse OpenAttachment(string id)
{
var attachment = Database.Retrieve(id);
// Read file from Magentrix Storage
byte[] fileBytes = Storage.ReadFileBytes(id);
return File(fileBytes, attachment.ContentType, attachment.Name);
}
File Response with Stream
public ActionResponse DownloadLargeFile()
{
var fileStream = GetLargeFileStream();
return File(fileStream, "application/octet-stream", "largefile.dat");
}
Open File Inline (in Browser)
By default, files are downloaded. To open them in the browser:
public ActionResponse ViewPdf(string id)
{
byte[] pdfBytes = GetPdfContent(id);
// Fourth parameter: true = open inline, false = download
return File(pdfBytes, "application/pdf", "document.pdf", true);
}
Available File() Overloads
// Basic file download
File(byte[] fileContents, string contentType)
// File download with filename
File(byte[] fileContents, string contentType, string fileName)
// File with inline/download control
File(byte[] fileContents, string contentType, string fileName, bool openInline)
// Stream-based file download
File(Stream fileStream, string contentType)
// Stream with filename
File(Stream fileStream, string contentType, string fileName)
StorageFile Response
Stream files directly from Magentrix Cloud Storage without loading them into memory.
Basic StorageFile
public ActionResponse DownloadStorageFile(string blobId)
{
return StorageFile(blobId, "application/pdf");
}
StorageFile with Filename
public ActionResponse OpenAttachment(string id)
{
var attachment = Database.Retrieve<NoteAndAttachment>(id);
return StorageFile(id, attachment.ContentType, attachment.Name);
}
StorageFile Opened Inline
public ActionResponse ViewDocument(string id)
{
var document = Database.Retrieve<Document>(id);
// Fourth parameter: true = open inline in browser
return StorageFile(id, document.ContentType, document.Name, true);
}
Available StorageFile() Overloads
// Basic storage file stream
StorageFile(string blobId, string contentType)
// Storage file with filename
StorageFile(string blobId, string contentType, string fileName)
// Storage file with inline/download control
StorageFile(string blobId, string contentType, string fileName, bool inline)
💡
When to use StorageFile vs File:- StorageFile: When file is already stored in Magentrix Cloud Storage
- File: When generating files dynamically or reading from external sources
PDF Response
Generate dynamic PDF files from HTML content or Active Page models.
PDF from Current Active Page
public ActionResponse ExportToPdf()
{
var accounts = Database.Query<Account>().Limit(5).ToList();
// Renders current Active Page as PDF
return Pdf(accounts, "accounts_list.pdf");
}
💡 Note: The Active Page associated with this controller is converted to PDF.
PDF from String Content
public ActionResponse GenerateSimplePdf()
{
string htmlContent = @"
<html>
<head>
<style>
body { font-family: Arial; }
h1 { color: navy; }
</style>
</head>
<body>
<h1>Sales Report</h1>
<p>Total Revenue: $1,234,567</p>
</body>
</html>
";
return Pdf(htmlContent, "sales_report.pdf");
}
PDF from Model with Custom HTML
public ActionResponse InvoicePdf(string opportunityId)
{
var opp = Database.Retrieve<Opportunity>(opportunityId);
var account = Database.Retrieve<Account>(opp.AccountId);
var invoiceHtml = $@"
<html>
<head>
<style>
body {{ font-family: Arial; padding: 20px; }}
.header {{ border-bottom: 2px solid navy; padding-bottom: 10px; }}
.total {{ font-size: 20px; font-weight: bold; }}
</style>
</head>
<body>
<div class='header'>
<h1>Invoice</h1>
<p>Date: {DateTime.Now:MM/dd/yyyy}</p>
</div>
<h2>Bill To:</h2>
<p>{account.Name}</p>
<p>{account.BillingStreet}</p>
<h2>Invoice Details:</h2>
<p>Opportunity: {opp.Name}</p>
<p class='total'>Total: ${opp.Amount:N2}</p>
</body>
</html>
";
return Pdf(invoiceHtml, $"Invoice_{opp.Name}.pdf");
}
PDF with Options
Control PDF generation with PdfOptions:
public ActionResponse CustomPdf()
{
var accounts = Database.Query<Account>().ToList();
var options = new PdfOptions
{
PageSize = PdfPageSize.Letter,
Orientation = PdfOrientation.Landscape,
MarginTop = 20,
MarginBottom = 20,
MarginLeft = 15,
MarginRight = 15,
CompressionLevel = PdfCompressionLevel.High
};
return Pdf(accounts, "accounts.pdf", options);
}
PDF Opened Inline
public ActionResponse ViewPdfInline()
{
var data = GetReportData();
// Fourth parameter: true = open in browser, false = download
return Pdf(data, "report.pdf", null, true);
}
Available Pdf() Overloads
// PDF from model using current Active Page
Pdf(object model, string filename)
// PDF from model with options
Pdf(object model, string filename, PdfOptions exportOptions)
// PDF from model with inline control
Pdf(object model, string filename, PdfOptions exportOptions, bool inline)
// PDF from HTML string
Pdf(string content, string filename)
// PDF from HTML with options
Pdf(string content, string filename, PdfOptions exportOptions)
// PDF from HTML with inline control
Pdf(string content, string filename, PdfOptions exportOptions, bool inline)
Important PDF Notes
⚠ Use Absolute Paths for Resources: When generating PDFs, CSS and images must use absolute URLs:
<!-- ❌ Relative paths don't work in PDFs -->
<img src="/images/logo.png" />
<link rel="stylesheet" href="/css/styles.css" />
<!-- ✅ Use absolute paths -->
<img src="https://yourportal.magentrix.com/images/logo.png" />
<link rel="stylesheet" href="https://yourportal.magentrix.com/css/styles.css" />
Data Responses
Json Response
Return JSON data for APIs and AJAX calls.
Basic JSON Response:
[HandleExceptionsForJson]
public ActionResponse GetContacts()
{
var contacts = Database.Query<Contact>().ToList();
return Json(contacts, JsonRequestBehavior.AllowGet);
}
⚠ Always use [HandleExceptionsForJson] attribute to ensure errors are returned as JSON.
JSON for POST Requests:
[HttpPost]
[HandleExceptionsForJson]
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 });
}
JSON with Custom Status Code:
[HandleExceptionsForJson]
public ActionResponse GetContact(string id)
{
Contact contact = Database.Retrieve<Contact>(id);
if (contact == null)
{
return Json(new { error = "Contact not found" }, 404);
}
return Json(contact, JsonRequestBehavior.AllowGet);
}
Available Json() Overloads:
// POST only (default behavior)
Json(object model)
// POST with custom status code
Json(object model, int statusCode)
// GET or POST based on behavior parameter
Json(object model, JsonRequestBehavior behavior)
// GET or POST with custom status code
Json(object model, int statusCode, JsonRequestBehavior behavior)
// POST with custom content type
Json(object model, string contentType)
// POST with content type and encoding
Json(object model, string contentType, Encoding contentEncoding)
// Simplified overload for GET requests
Json(object model, bool allowGet)
💡 Use JsonRequestBehavior.AllowGet to allow GET requests to return JSON.
Content Response
Return plain text, HTML, XML, CSS, JavaScript, or any text-based content.
Plain Text:
public ActionResponse GetText()
{
return Content("Hello, World!");
}
HTML Content:
public ActionResponse GetHtml()
{
string html = "<h1>Welcome</h1><p>This is dynamic HTML.</p>";
return Content(html, "text/html");
}
XML Content:
public ActionResponse GetXml()
{
string xml = @"
<contact>
<firstName>John</firstName>
<lastName>Doe</lastName>
<email>john.doe@example.com</email>
</contact>
";
return Content(xml, "application/xml");
}
JSON as Content (Alternative to Json()):
public ActionResponse GetJsonAsText()
{
var data = new { name = "John", age = 30 };
var json = JsonConvert.SerializeObject(data);
return Content(json, "application/json");
}
CSS Content:
public ActionResponse GetCustomCss()
{
var css = @"
body { background-color: #f0f0f0; }
h1 { color: navy; }
";
return Content(css, "text/css");
}
JavaScript Content:
public ActionResponse GetScript()
{
var js = "alert('Hello from controller!');";
return Content(js, "application/javascript");
}
Available Content() Overloads:
// Plain text with default content type
Content(string content)
// Content with specific MIME type
Content(string content, string contentType)
// Content with MIME type and encoding
Content(string content, string contentType, Encoding contentEncoding)
💡 Note: If your action returns a primitive type (string, int, etc.), Magentrix automatically converts it to a Content response:
public string MyAction()
{
return "<Contact><FirstName>Sam</FirstName></Contact>";
}
This is equivalent to:
public ActionResponse MyAction()
{
return Content("<Contact><FirstName>Sam</FirstName></Contact>");
}
Special Responses
UnauthorizedAccessResponse
Return when a user doesn't have permission to access a resource:
public ActionResponse AdminDashboard()
{
if (SystemInfo.IsGuestUser)
return UnauthorizedAccessResponse();
if (UserInfo.IsPortalUser)
return UnauthorizedAccessResponse();
// Employee-only logic
return View();
}
PageNotFound
Return a user-friendly 404 error:
public ActionResponse ViewResource(string id)
{
var resource = Database.Query<Resource>()
.Where(r => r.Id == id)
.FirstOrDefault();
if (resource == null)
return PageNotFound();
return View(resource);
}
RedirectToError
Redirect to an error page with a custom message:
public ActionResponse ProcessPayment()
{
try
{
// Payment processing logic
return View();
}
catch (PaymentException ex)
{
return RedirectToError("Payment processing failed. Please try again or contact support.");
}
}
IrisData Response
IrisData() serializes any data you pass it — a single record, a list of records, or a composite shape like new { account, contacts, ... } — to JSON for the Iris UI tier. The payload automatically includes entity metadata for every model passed, so the Iris client can render forms, populate picklists, and enforce read-only fields without separate metadata round-trips. Per-model permission checks are opt-in and, when enabled, the resulting flags let the client gate edit and delete controls per record.
Slightly more prose-y:IrisData() returns a JSON payload tailored for the Iris UI tier. It accepts a single record, a list of records, or a composite of several models — for example, new { account, contacts, ... } — and automatically attaches the entity metadata for each model in the payload, so the Iris client can render forms, populate picklists, and enforce read-only fields without additional round trips. Permission checks can optionally be enabled per model; when they are, the payload includes per-record permission flags the client uses to gate edit and delete controls.
Use this response from any controller action that serves the Iris UI. For ordinary AJAX endpoints that return plain data with no metadata or permission expectations, use Json() instead.
Basic IrisData Response
Wrap one or more named values in an anonymous object and pass it to IrisData():
public class ContactApiController : AspxAsyncController
{
[Authorize]
public async Task<ActionResponse> Detail(string id)
{
if (string.IsNullOrEmpty(id))
throw new ModelValidationException("id is required.");
var contact = await Database.RetrieveAsync<Contact>(id);
if (contact == null)
throw new ModelValidationException("Contact not found.");
return IrisData(contact);
}
}
The client receives a JSON object with a contact key containing the record's readable fields, plus three auxiliary keys generated automatically:
__entityTypes — maps each top-level key to its entity name (for example, { "contact": "Contact" }).__permissions — per-key record-level permission flags. Populated only when permissions are requested (see below).__metadatas — EntityMetadata for the model entity and every reference entity actually populated on the records, including loaded picklist values and Record Type metadata.
Returning a Collection
Pass an IEnumerable<dbObject> the same way:
[Authorize]
public async Task<ActionResponse> List()
{
var contacts = await Database.Query<Contact>()
.OrderBy(c => c.LastName)
.ToListAsync();
return IrisData(contacts);
}
Each record in the list is field-filtered against the user's read permissions; non-readable fields are stripped before serialization.
Returning Multiple Models in One Response
Multiple keys in the same anonymous object produce multiple top-level entries in the JSON. The platform builds __entityTypes, __permissions, and __metadatas entries for each:
[Authorize]
public async Task<ActionResponse> Dashboard()
{
var account = await Database.RetrieveAsync<Account>(SystemInfo.User.AccountId);
var contacts = await Database.Query<Contact>()
.Where(f => f.AccountId == SystemInfo.User.AccountId)
.Select(f => new { f.FirstName, f.LastName, f.Email }
.ToListAsync();
return IrisData(new { account, contacts });
}
Including Record Permissions
Pass a With flag to request per-record permission evaluation. The result is added to the response's __permissions dictionary so the Iris client can show or hide Edit, Delete, and New controls.
Apply permissions globally (the same flags for every entity property in the model):
[Authorize]
public async Task<ActionResponse> Detail(string id)
{
if (string.IsNullOrEmpty(id))
throw new ModelValidationException("id is required.");
var contact = await Database.RetrieveAsync<Contact>(id);
if (contact == null)
throw new ModelValidationException("Contact not found.");
return IrisData(
contact,
With.Update | With.Delete);
}
Available flags:
| Flag | What It Adds to __permissions |
|---|
With.Create | create boolean — whether the user is permitted to create a new record of this entity. Evaluated once per key, based on the first record. |
With.Update | items[].update boolean per record — whether the current user can update each specific record. |
With.Delete | items[].delete boolean per record — whether the current user can delete each specific record. |
Flags can be combined with the bitwise OR operator: With.Create | With.Update | With.Delete.
Per-Property Permissions
When different model properties need different permission flags, wrap each value in IrisDataWithPermission instead of passing a global flag:
[Authorize]
public async Task<ActionResponse> Dashboard()
{
var account = await Database.RetrieveAsync<Account>(SystemInfo.User.AccountId);
var contacts = await Database.Query<Contact>()
.Where(c => c.AccountId == SystemInfo.User.AccountId)
.ToListAsync();
return IrisData(new
{
account = new IrisDataWithPermission(account, With.Update),
contacts = new IrisDataWithPermission(contacts, With.Create | With.Update | With.Delete)
});
}
Mixing a global With argument with per-property IrisDataWithPermission wrappers is not allowed and raises an ArgumentException. Use one or the other.
What Gets Included in the Payload
- Readable fields only. Each field is checked against the entity's metadata; fields the current user cannot read are stripped.
- Reference metadata is included on demand. If a record actually has a non-null value on a reference field, the response automatically includes the referenced entity's metadata. Empty references are skipped to keep the payload small.
- Person references are resolved. Lookups whose target is
Person (Account, Contact, or User) are resolved to the underlying entity using the [DataAssociation] attribute on the foreign-key property. - Picklist values and Record Types are loaded on every entity's metadata before it is returned, so the Iris client doesn't need a second round trip to populate dropdowns or branch by Record Type.
Non-Entity Values
Properties that are not dbObject instances or dbObject collections are passed through unchanged. This is useful for mixing data with primitives, dictionaries, or your own DTOs in a single response:
[Authorize]
public async Task<ActionResponse> Detail(string id)
{
if (string.IsNullOrEmpty(id))
throw new ModelValidationException("id is required.");
var contact = await Database.RetrieveAsync<Contact>(id);
if (contact == null)
throw new ModelValidationException("Contact not found.");
var uniqueValue = SystemInfo.UserId + contact.Id;
return IrisData(new { contact, uniqueValue });
}
Here uniqueValue is a string and is serialized verbatim; no metadata is generated for it, and it does not appear in __entityTypes or __permissions.
Content Type and Async Execution
The response writes Content-Type: application/json with UTF-8 encoding. Both synchronous (Execute) and asynchronous (ExecuteAsync) execution are supported — the framework chooses based on the request pipeline. No additional configuration is required.
When to Use IrisData vs. Json
| Use | When |
|---|
IrisData() | The endpoint serves the Iris (Vue 3) UI and the client expects metadata, permission flags, and entity-typed records. |
Json() | The endpoint returns a plain API response — a public REST endpoint, an AJAX call from a legacy ASCX page, or any case where the caller doesn't need or want the metadata envelope. |
Response Type Decision Guide
Use this guide to choose the right response type:
┌─────────────────────────────────────┐
│ What are you trying to do? │
└──────────────┬──────────────────────┘
│
┌──────┴──────┐
│ │
┌─────▼──────┐ ┌──▼───────────────┐
│ Show HTML │ │ Return Data │
│ Page? │ │ or Files? │
└─────┬──────┘ └───┬──────────────┘
│ │
│ │
┌───────▼─────────────▼────────────────┐
│ │
│ HTML Page Options: │
│ • View() - Full page │
│ • PartialView() - Widget/fragment │
│ • Predefined views - Error pages │
│ │
│ Data/File Options: │
│ • Json() - API responses │
│ • File() - Download files │
│ • StorageFile() - Cloud files │
│ • Pdf() - Generate PDFs │
│ • Content() - Text/HTML/XML │
│ │
│ Navigation Options: │
│ • Redirect() - Go to URL │
│ • RedirectToAction() - Go to action │
│ │
│ Error Options: │
│ • UnauthorizedAccessResponse() │
│ • PageNotFound() │
│ • RedirectToError() │
└──────────────────────────────────────┘
Practical Examples
Example 1: Complete CRUD Controller with Mixed Responses
public class ContactManagerController: AspxController
{
// GET: List all contacts (View response)
public override ActionResponse Index()
{
var contacts = Database.Query<Contact>()
.OrderBy(c => c.LastName)
.ToList();
return View(contacts);
}
// GET: Show create form (View response)
public ActionResponse New()
{
return View(new Contact());
}
// POST: Create contact (Redirect response)
[HttpPost]
public ActionResponse New(Contact model)
{
if (ModelState.IsValid)
{
Database.Insert(model);
AspxPage.AddMessage("Contact created successfully!");
return RedirectToAction("Details", new { id = model.Id });
}
return View(model);
}
// GET: Show contact details (View response)
public ActionResponse Details(string id)
{
var contact = Database.Retrieve(id);
if (contact == null)
return PageNotFound();
return View(contact);
}
// GET: Show edit form (View response)
public ActionResponse Edit(string id)
{
Contact contact = Database.Retrieve(id);
if (contact == null)
return PageNotFound();
return View(contact);
}
// POST: Update contact (Redirect response)
[HttpPost]
public ActionResponse Edit(Contact model)
{
// send the control back to UI, ModelState contains the errors.
if (!ModelState.IsValid)
return View();
try
{
Database.Edit(model);
AspxPage.AddMessage("Contact updated successfully!");
}
catch (DatabaseException ex)
{
AspxPage.AddMessage(ex.Message);
return View();
}
return View(model);
}
// POST: Delete contact (Redirect response)
[HttpPost]
public ActionResponse Delete(string id)
{
try
{
Database.Delete(id);
AspxPage.AddMessage("Contact deleted successfully.");
return Redirect("/aspx/mylist");
}
catch (DatabaseException ex)
{
return RedirectToError("An error occurred while deleting the contact.");
}
}
// GET: Export to CSV (File response)
public ActionResponse ExportCsv()
{
var contacts = Database.Query<Contact>().ToList();
var csv = new StringBuilder();
csv.AppendLine("First Name,Last Name,Email,Phone");
foreach (var contact in contacts)
csv.AppendLine($"{contact.FirstName},{contact.LastName},{contact.Email},{contact.Phone}");
var bytes = Encoding.UTF8.GetBytes(csv.ToString());
return File(bytes, "text/csv", "contacts.csv");
}
// GET: Export to PDF (PDF response)
public ActionResponse ExportPdf()
{
var contacts = Database.Query<Contact>().ToList();
return Pdf(contacts, "contacts.pdf");
}
}
Example 2: API Controller with JSON Responses
public class ContactApiController: AspxAsyncController
{
// GET: /acls/ContactApi/GetAll
[HandleExceptionsForJson]
public async Task<ActionResponse> GetAll()
{
var contacts = await Database.Query<Contact>()
.OrderBy(c => c.LastName)
.ToListAsync();
return Json(contacts, JsonRequestBehavior.AllowGet);
}
// GET: /acls/ContactApi/Get?id=003R000000haXk3IAE
public async Task<ActionResponse> Get(string id)
{
var contact = await Database.RetrieveAsync<Contact>(id);
if (contact == null)
throw new ModelValidationException("Contact not found");
return Json(contact, JsonRequestBehavior.AllowGet);
}
// POST: /acls/ContactApi/Create
[HttpPost]
public async Task<ActionResponse> Create(Contact model)
{
await Database.InsertAsync(model);
return Json(new { success = true, id = model.Id, message = "Contact created successfully" });
}
// POST: /acls/ContactApi/Update
[HttpPost]
public async Task<ActionResponse> Update(Contact model)
{
var existing = await Database.RetrieveAsync<Contact>(model.Id);
if (existing == null)
throw new ModelValidationException("Contact not found");
await Database.UpdateAsync(model);
return Json(new { success = true, message = "Contact updated successfully" });
}
// POST: /acls/ContactApi/Delete?id=003R000000haXk3IAE
[HttpPost]
public async Task<ActionResponse> Delete(string id)
{
var contact = await Database.RetrieveAsync<Contact>(id);
if (contact == null)
throw new ModelValidationException("Contact not found");
await Database.DeleteAsync(contact);
return Json(new { success = true, message = "Contact deleted successfully" });
}
// GET: /acls/ContactApi/Search?query=john
public async Task<ActionResponse> Search(string query)
{
if (string.IsNullOrEmpty(query))
throw new ModelValidationException("Contact not found");
var contacts = await Database.Query<Contact>()
.Where(c => c.FirstName.Contains(query)
|| c.LastName.Contains(query)
|| c.Email.Contains(query))
.OrderBy(c => c.LastName)
.ToListAsync();
return Json(new { success = true, count = contacts.Count, data = contacts }, JsonRequestBehavior.AllowGet);
}
}
Example 3: Report Generator with Multiple Export Formats
public class ReportsController: AspxController
{
// GET: /aspx/Reports - Show report page (View response)
public override ActionResponse Index()
{
var accounts = Database.Query<Account>()
.Where(a => a.Type == "Customer")
.ToList();
return View(accounts);
}
// GET: /acls/Reports/ExportCsv - CSV export (File response)
public ActionResponse ExportCsv(string type)
{
var accounts = Database.Query<Account>()
.Where(a => a.Type == type)
.OrderBy(a => a.Name)
.ToList();
var csv = new StringBuilder();
csv.AppendLine("Account Name,Type,Industry,Annual Revenue");
foreach (var account in accounts)
csv.AppendLine($"{account.Name},{account.Type},{account.Industry},{account.AnnualRevenue}");
byte[] bytes = Encoding.UTF8.GetBytes(csv.ToString());
return File(bytes, "text/csv", $"accounts_{type}.csv");
}
// GET: /acls/Reports/ExportPdf - PDF export (PDF response)
public ActionResponse ExportPdf(string type)
{
var accounts = Database.Query<Account>()
.Where(a => a.Type == type)
.OrderBy(a => a.Name)
.ToList();
var html = GenerateReportHtml(accounts, type);
return Pdf(html, $"accounts_{type}.pdf");
}
// GET: /acls/Reports/ExportJson - JSON export (Json response)
[HandleExceptionsForJson]
public ActionResponse ExportJson(string type)
{
var accounts = Database.Query<Account>()
.Where(a => a.Type == type)
.OrderBy(a => a.Name)
.Select(a => new
{
a.Name,
a.Type,
a.Industry,
a.AnnualRevenue,
a.Phone,
a.Website
})
.ToList();
return Json(new {
reportType = type,
generatedDate = DateTime.Now,
totalCount = accounts.Count,
data = accounts
}, JsonRequestBehavior.AllowGet);
}
// GET: /acls/Reports/ExportXml - XML export (Content response)
public ActionResponse ExportXml(string type)
{
var accounts = Database.Query<Account>()
.Where(a => a.Type == type)
.OrderBy(a => a.Name)
.ToList();
var xml = new StringBuilder();
xml.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
xml.AppendLine("<accounts>");
foreach (var account in accounts)
{
xml.AppendLine(" <account>");
xml.AppendLine($" <name>{SecurityElement.Escape(account.Name)}</name>");
xml.AppendLine($" <type>{SecurityElement.Escape(account.Type)}</type>");
xml.AppendLine($" <industry>{SecurityElement.Escape(account.Industry)}</industry>");
xml.AppendLine($" <revenue>{account.AnnualRevenue}</revenue>");
xml.AppendLine(" </account>");
}
xml.AppendLine("</accounts>");
return Content(xml.ToString(), "application/xml");
}
private string GenerateReportHtml(List<Account> accounts, string type)
{
return $@"
<html>
<head>
<style>
body {{ font-family: Arial; padding: 20px; }}
h1 {{ color: navy; border-bottom: 2px solid navy; }}
table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background-color: navy; color: white; }}
.footer {{ margin-top: 20px; font-size: 12px; color: gray; }}
</style>
</head>
<body>
<h1>{type} Accounts Report</h1>
<p>Generated: {DateTime.Now:MM/dd/yyyy hh:mm tt}</p>
<p>Total Accounts: {accounts.Count}</p>
<table>
<thead>
<tr>
<th>Account Name</th>
<th>Industry</th>
<th>Annual Revenue</th>
</tr>
</thead>
<tbody>
{string.Join("", accounts.Select(a => $@"
<tr>
<td>{a.Name}</td>
<td>{a.Industry}</td>
<td>${a.AnnualRevenue:N2}</td>
</tr>
"))}
</tbody>
</table>
<div class='footer'>
<p>Generated by Magentrix Portal</p>
</div>
</body>
</html>
";
}
}
Example 4: File Management Controller
public class FileManagerController: AspxController
{
// GET: /aspx/FileManager - Show file list (View response)
public override ActionResponse Index()
{
var files = Database.Query<Document>()
.OrderByDescending(d => d.CreatedDate)
.ToList();
return View(files);
}
// GET: /acls/FileManager/Download?id=xxx - Download file (File response)
public ActionResponse Download(string id)
{
try
{
var document = Database.Retrieve(id);
if (document == null)
return PageNotFound();
// Read file from storage
var fileBytes = Storage.ReadFileBytes(document.StorageId);
return File(fileBytes, document.ContentType, document.FileName);
}
catch (Exception ex)
{
SystemInfo.Debug($"Download error: {ex.Message}");
return RedirectToError("An error occurred while downloading the file.");
}
}
// GET: /acls/FileManager/DownloadFromStorage?blobId=xxx - Stream from storage (StorageFile response)
public ActionResponse DownloadFromStorage(string blobId)
{
try
{
var document = Database.Query<Document>()
.Where(d => d.StorageId == blobId)
.FirstOrDefault();
if (document == null)
return PageNotFound();
// Stream directly from cloud storage
return StorageFile(blobId, document.ContentType, document.FileName);
}
catch (Exception ex)
{
SystemInfo.Debug($"Storage download error: {ex.Message}");
return RedirectToError("An error occurred while accessing the file.");
}
}
// GET: /acls/FileManager/ViewPdf?id=xxx - View PDF inline (StorageFile response)
public ActionResponse ViewPdf(string id)
{
var document = Database.Retrieve<Document>(id);
if (document == null)
return PageNotFound();
if (document.ContentType != "application/pdf")
return RedirectToError("Only PDF files can be viewed inline.");
// Fourth parameter: true = open inline in browser
return StorageFile(document.StorageId, document.ContentType, document.FileName, true);
}
// POST: /acls/FileManager/Upload - Upload file (Json response)
[HttpPost]
[HandleExceptionsForJson]
public ActionResponse Upload(HttpPostedFileBase file, string description)
{
try
{
if (file == null || file.ContentLength == 0)
return Json(new { success = false, error = "No file uploaded" }, 400);
// Read file content
var fileBytes = new byte[file.ContentLength];
file.InputStream.Read(fileBytes, 0, file.ContentLength);
// Save to storage
var storageId = Storage.WriteFile(fileBytes, file.FileName, file.ContentType);
// Create document record
var document = new Document
{
FileName = file.FileName,
ContentType = file.ContentType,
FileSize = file.ContentLength,
StorageId = storageId,
Description = description,
UploadedBy = UserInfo.Name,
UploadedDate = DateTime.Now
};
Database.Insert(document);
return Json(new {
success = true,
documentId = document.Id,
message = "File uploaded successfully"
});
}
catch (Exception ex)
{
SystemInfo.Debug($"Upload error: {ex.Message}");
return Json(new { success = false, error = "Upload failed" }, 500);
}
}
}
Example 5: Dashboard with AJAX Integration
public class DashboardController: AspxController
{
// GET: /aspx/Dashboard - Main dashboard page (View response)
public override ActionResponse Index()
{
return View();
}
// GET: /acls/Dashboard/GetStats - Get dashboard statistics (Json response)
[HandleExceptionsForJson]
public ActionResponse GetStats()
{
var stats = new
{
totalAccounts = Database.Query<Account>().Count(),
totalContacts = Database.Query<Contact>().Count(),
openOpportunities = Database.Query<Opportunity>()
.Where(o => !o.IsClosed)
.Count(),
totalRevenue = Database.Query<Opportunity>()
.Where(o => o.Stage == "Closed Won")
.ToList()
.Sum(o => o.Amount),
thisMonthRevenue = Database.Query<Opportunity>()
.Where(o => o.Stage == "Closed Won" &&
o.CloseDate >= DateTime.Now.AddMonths(-1))
.ToList()
.Sum(o => o.Amount)
};
return Json(stats, JsonRequestBehavior.AllowGet);
}
// GET: /acls/Dashboard/GetRecentActivity - Get recent activities (Json response)
[HandleExceptionsForJson]
public ActionResponse GetRecentActivity(int count = 10)
{
var recentOpportunities = Database.Query<Opportunity>()
.OrderByDescending(o => o.LastModifiedDate)
.Take(count)
.Select(o => new
{
id = o.Id,
name = o.Name,
stage = o.Stage,
amount = o.Amount,
accountName = o.Account.Name,
lastModified = o.LastModifiedDate
})
.ToList();
return Json(new {
success = true,
data = recentOpportunities
}, JsonRequestBehavior.AllowGet);
}
// GET: /acls/Dashboard/ExportSnapshot - Export dashboard snapshot (PDF response)
public ActionResponse ExportSnapshot()
{
var stats = new
{
TotalAccounts = Database.Query<Account>().Count(),
TotalContacts = Database.Query<Contact>().Count(),
OpenOpportunities = Database.Query<Opportunity>().Where(o => !o.IsClosed).Count(),
TotalRevenue = Database.Query<Opportunity>()
.Where(o => o.Stage == "Closed Won")
.ToList()
.Sum(o => o.Amount)
};
var html = $@"
<html>
<head>
<style>
body {{ font-family: Arial; padding: 20px; }}
h1 {{ color: navy; }}
.metric {{ background: #f0f0f0; padding: 15px; margin: 10px 0; }}
.metric-value {{ font-size: 24px; font-weight: bold; color: navy; }}
</style>
</head>
<body>
<h1>Dashboard Snapshot</h1>
<p>Generated: {DateTime.Now:MM/dd/yyyy hh:mm tt}</p>
<div class='metric'>
<div>Total Accounts</div>
<div class='metric-value'>{stats.TotalAccounts}</div>
</div>
<div class='metric'>
<div>Total Contacts</div>
<div class='metric-value'>{stats.TotalContacts}</div>
</div>
<div class='metric'>
<div>Open Opportunities</div>
<div class='metric-value'>{stats.OpenOpportunities}</div>
</div>
<div class='metric'>
<div>Total Revenue (Closed Won)</div>
<div class='metric-value'>${stats.TotalRevenue:N2}</div>
</div>
</body>
</html>
";
return Pdf(html, $"Dashboard_Snapshot_{DateTime.Now:yyyyMMdd}.pdf");
}
}
JavaScript in Active Page calls the AJAX endpoints:
// Fetch dashboard statistics
fetch('/acls/Dashboard/GetStats')
.then(response => response.json())
.then(data => {
document.getElementById('totalAccounts').textContent = data.totalAccounts;
document.getElementById('totalContacts').textContent = data.totalContacts;
document.getElementById('openOpportunities').textContent = data.openOpportunities;
document.getElementById('totalRevenue').textContent = '$' + data.totalRevenue.toLocaleString();
});
// Fetch chart data
fetch('/acls/Dashboard/GetChartData?chartType=revenue-by-month')
.then(response => response.json())
.then(result => {
if (result.success) {
renderChart(result.data);
}
});
Best Practices
1. Choose the Right Response Type
✅ Use View() for user-facing pages
public override ActionResponse Index()
{
return View(model);
}
✅ Use Json() for APIs
[HandleExceptionsForJson]
public ActionResponse GetData()
{
return Json(data, JsonRequestBehavior.AllowGet);
}
✅ Use Redirect() after POST to prevent resubmission
[HttpPost]
public ActionResponse Create(Contact model)
{
Database.Insert(model);
return RedirectToAction("Index"); // ✅ Correct
// return View(model); // ❌ Avoid - causes form resubmission
}
2. Always Use [HandleExceptionsForJson] for JSON Responses
❌ Without attribute:
public ActionResponse GetContact(string id)
{
var contact = Database.Retrieve<Contact>(id);
return Json(contact, JsonRequestBehavior.AllowGet);
}
// If an exception occurs, response is HTML error page, not JSON
✅ With attribute:
[HandleExceptionsForJson]
public ActionResponse GetContact(string id)
{
var contact = Database.Retrieve<Contact>(id);
return Json(contact, JsonRequestBehavior.AllowGet);
}
// Exceptions are automatically returned as JSON
3. Use Absolute URLs for PDF Resources
❌ Incorrect:
<img src="/images/logo.png" />
<link rel="stylesheet" href="/css/styles.css" />
✅ Correct:
<img src="https://yourportal.magentrix.com/images/logo.png" />
<link rel="stylesheet" href="https://yourportal.magentrix.com/css/styles.css" />
4. Set Appropriate Content Types
// CSV files
return File(bytes, "text/csv", "data.csv");
// Excel files
return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "data.xlsx");
// JSON text
return Content(json, "application/json");
// XML text
return Content(xml, "application/xml");
5. Use StorageFile for Cloud-Stored Files
❌ Inefficient:
public ActionResponse Download(string id)
{
// Loads entire file into memory
var fileBytes = Storage.ReadFileBytes(id);
return File(fileBytes, contentType, fileName);
}
✅ Efficient:
public ActionResponse Download(string id)
{
// Streams directly
return StorageFile(id, contentType, fileName);
}
6. Provide Meaningful Filenames
❌ Generic:
return File(bytes, "text/csv", "export.csv");
✅ Descriptive:
return File(bytes, "text/csv", $"Contacts_Export_{DateTime.Now:yyyyMMdd}.csv");
7. Handle Errors Gracefully
public ActionResponse Download(string id)
{
try
{
var document = Database.Retrieve<Document>(id);
if (document == null)
return PageNotFound();
var fileBytes = Storage.ReadFileBytes(document.StorageId);
return File(fileBytes, document.ContentType, document.FileName);
}
catch (Exception ex)
{
SystemInfo.Error(ex);
return RedirectToError("An error occurred while downloading the file.");
}
}
Common Pitfalls
❌ Pitfall 1: Using View() in Standalone Controllers
// URL: /acls/MyApi/GetData
public class MyApiController: AspxController
{
public ActionResponse GetData()
{
// ❌ ERROR: No Active Page exists!
return View();
}
}
Solution:
[HandleExceptionsForJson]
public ActionResponse GetData()
{
var data = Database.Query<Contact>().ToList();
// ✅ Correct
return Json(data, JsonRequestBehavior.AllowGet);
}
❌ Pitfall 2: Returning View After POST
[HttpPost]
public ActionResponse Create(Contact model)
{
Database.Insert(model);
// ❌ Causes form resubmission on refresh
return View(model);
}
Solution:
[HttpPost]
public ActionResponse Create(Contact model)
{
Database.Insert(model);
// ✅ Correct - PRG pattern
return RedirectToAction("Index");
}
❌ Pitfall 3: Forgetting JsonRequestBehavior.AllowGet
public ActionResponse GetContacts()
{
var contacts = Database.Query<Contact>().Limit(10).ToList();
// ❌ Only works for POST
return Json(contacts);
}
Solution:
[HandleExceptionsForJson]
public ActionResponse GetContacts()
{
var contacts = Database.Query<Contact>().Limit(10).ToList();
// ✅ Works for GET
return Json(contacts, JsonRequestBehavior.AllowGet);
}
❌ Pitfall 4: Not Checking for Null Before Returning View
public ActionResponse Details(string id)
{
var contact = Database.Retrieve<Contact>(id);
// ❌ Crashes if contact is null
return View(contact);
}
Solution:
public ActionResponse Details(string id)
{
var contact = Database.Retrieve<Contact>(id);
// ✅ Graceful error handling
if (contact == null)
return PageNotFound();
return View(contact);
}
Summary
This section covered all response types available in Magentrix controllers:
✅ View Responses - Render Active Pages for user-facing interfaces
✅ PartialView Responses - Render page fragments and widgets
✅ Redirect Responses - Navigate to URLs or controller actions
✅ File Responses - Stream binary files for download
✅ StorageFile Responses - Stream files from Magentrix Cloud Storage
✅ PDF Responses - Generate dynamic PDF documents
✅ Json Responses - Return JSON data for APIs
✅ Content Responses - Return text, HTML, XML, or other content
✅ Special Responses - Handle errors and unauthorized access
Next Steps
Continue your learning journey:
Quick Reference
Response Type Cheat Sheet
// View responses
return View();
return View(model);
return PartialView(model);
return PageNotFound();
return RecordDeleted();
// Redirect responses
return Redirect("~/home/index");
return RedirectToAction("Index");
return RedirectToAction("Edit", "Contact", new { id = "123" });
return Redirect(ActivePages.MyPage, new { id = "123" });
// File responses
return File(bytes, "text/csv", "data.csv");
return StorageFile(blobId, contentType, fileName);
return Pdf(model, "report.pdf");
// Data responses
return Json(data, JsonRequestBehavior.AllowGet);
return Content("Hello World");
return Content(html, "text/html");
// Error responses
return UnauthorizedAccessResponse();
return RedirectToError("Error message");
💡 Mastering response types is essential for building robust controllers! Choose the right response type for each scenario to create efficient, user-friendly applications.