Can't invoke client functions from the Server - asp.net-mvc

I'm working on a small concept trying to get to work with SignalR.
In this scenario all connected clients need to refresh when one of the clients perform an update, this update is performed through an Ajax request.
Because there might be different instances of the page, depending on a parameter, I'm using groups for this.
The client side code looks as followed:
<script>
window.onload = function () {
DoRefresh();
}
$(function () {
var hub = $.connection.commonHub;
hub.client.refresh = function () {
DoRefresh();//This doesn't get called for the other clients
};
$.connection.hub.start();
$.connection.hub.start().done(function () {
hub.server.join("MyPage" + #Model.Id + "");
});
});
function htmlEncode(value) {
var encodedValue = $('<div />').text(value).html();
return encodedValue;
}
function DoRefresh() {
$.ajax({
url: '#Url.Action("LoadData")',
cache: false,
data: "&id=" + #Model.Id + "",
success: function (html) {
$("#conversation").empty();
$("#conversation").append(html);
},
error: function (xhr, status, err) {
alert('Response code:' + xhr.status + '\r\n[Error:' + err + '] ' + status);
}
});
return false;
};
function DoUpdate() {
$.ajax({
url: '#Url.Action("DoUpdate")',
cache: false,
data: "&id=" + #Model.Id + "&posterID=" + #userAccount.AccountID + "&message=" + $("#Textbox").val().replaceAll("\n", "[br]"),
success: function () {
$("#Textbox").empty();
DoRefresh();
},
error: function () {
$("#Textbox").empty();
DoRefresh();
}
});
return false;
};
</script>
In my controller, following functions are part of this scenario:
public class MyController : Controller
{
private Hubs.CommonHub hub = new Hubs.CommonHub();
//Some other Controller Methods
public PartialViewResult LoadData(int id)
{
MyModel item = Connection.DB.MyData.FirstOrDefault(x => x.Id == id);
return PartialView(item);
}
public virtual EmptyResult DoUpdate(int id, int posterID, string message)
{
message = message.Replace("[br]", "\r\n");
MyModel item = Connection.DB.MyData.FirstOrDefault(x => x.Id == id);
Account poster = Connection.DB.Accounts.FirstOrDefault(x => x.Id == posterID);;
item.Updates.Add(new Update()
{
PosterId = posterID,
Poster = poster,
Id = id,
Item = item,
PostDate = DateTime.UtcNow,
Message = message.Replace(Environment.NewLine,"\r\n")
});
Connection.DB.SaveChanges();
hub.Refresh("MyPage" + item.Id);
return null;
}
}
And finally, my hub class looks like this:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.SignalR;
using MyProject.Models;
namespace MyProject.Hubs
{
public class CommonHub : Hub
{
private string myInfo;
public override Task OnConnected()
{
myInfo = Context.ConnectionId;
return base.OnConnected();
}
public Task Join(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task Leave(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public void Refresh(string groupName)
{
var context = GlobalHost.ConnectionManager.GetHubContext<CommonHub>();
context.Clients.Group(groupName).Refresh();
}
}
}
The Join task in the hub is called with every connect of a new browser window. The Refresh method is invoked on the hub, but only the invoking window has its page refreshed. The client side refresh function only gets invoked once when debugging, probably for the invoking client.
I have no idea why the other clients are not doing their updates, please help.

Related

Return to same View if Data is not Found and Display Not Found Message in the same View

I have Search View Where user enters Employee ID, Upon Clicking Button an Action Method is Executed and user is Redirected to Employee Details View.
If Employee Id not Matches I want to Retain the Same Search View and display a Label with Employee Details not Found Message in MVC,
Please help on doing the above Fucntionality in my Controller.
[HttpGet]
public async Task<ActionResult> Details(string firstName)
{
var empDetails = await _context.EmpDetails
.FirstOrDefaultAsync(m => m.FirstName == firstName);
if (empDetails == null)
{
// ???
}
return View(empDetails);
}
It is good practice to have ViewModel classes. Make one, and use it to transfer domain objects and messages to your view. Like this:
class EmpDetailsViewModel
{
public EmpDetail Emp { get;set; }
public string Message { get;set; }
}
[HttpGet]
public async Task<ActionResult> Details(string firstName)
{
var vm = new EmpDetailsViewModel();
var empDetails = await _context.EmpDetails
.FirstOrDefaultAsync(m => m.FirstName == firstName);
if (empDetails == null)
{
vm.Message = "Employee not found (or whatever)"
}
else
{
vm.Emp = empDetails;
}
return View(vm);
}
Then, in your view, just do
if (Model.Emp == null)
{
<span>#Model.Message</span>
}
else
{
<div>Emp details stuff </div>
}
What I understand is you want to return a message when Employee is not found.
Try this
[HttpGet]
public async Task<ActionResult> Details(string firstName)
{
var empDetails = await _context.EmpDetails
.FirstOrDefaultAsync(m => m.FirstName == firstName);
if (empDetails == null)
{
return Content("Employee not found");
}
return View(empDetails);
}
In view extract the message from the response.
--Edit
You can do an Ajax call like below to this action method
<script type="text/javascript">
$(function () {
$("#btnGetDetails").click(function () {
$.ajax({
type: "GET",
url: "/Employee/Details",
data: '{firstName: "' + $("#txtSearch").val() + '" }',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
alert(response.responseText);
},
failure: function (response) {
alert(response.responseText);
},
error: function (response) {
alert(response.responseText);
}
});
});
});
</script>
success call back will be triggered and the message will be available in the view. (Didn't test the script)

MVC SignalR not firing from Controller Post Method

When Saving schedule to calendar it must auto update the activity logs on my notification bar in my Home Controller. It saves the data but only show when notification bar is refreshed. It seems that Hub is not starting when saved.
CalendarController.cs
[HttpPost]
public JsonResult SaveSchedule(Schedule s)
{
var userid = User.Identity.GetUserId();
var profile = _context.Profiles.Single(p => p.Id == userid);
var status = false;
if (s.Schedule_ID > 0)
{
//Update
var v = _context.Schedules.Where(a => a.Schedule_ID == s.Schedule_ID).FirstOrDefault();
if (v != null)
{
v.Shift = s.Shift;
}
}
var activitylog = new ActivityLog
{
UserId = userid,
LogDate = DateTime.Now,
Activity = ActivityHelper.GetActivityLog(4, profile.FirstName)
};
// save to data and must be shown on notification bar
_context.ActivityLogs.Add(activitylog);
_context.SaveChanges();
ActivityHub.StartLogging();
status = true;
return new JsonResult { Data = new { status = status } };
}
HomeController.cs
public JsonResult GetLogs()
{
return Json(ActivityHelper.GetActivityLogs(), JsonRequestBehavior.AllowGet);
}
ActivityHub.cs
public class ActivityHub : Hub
{
public static void StartLogging()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<ActivityHub>();
//calls the signalR client part to execute javascript method
context.Clients.All.displayLog();
}
}
My CSHTML
<script>
$(function () {
var activityFromHub = $.connection.activityHub;
$.connection.hub.start().done(function () {
FetchLogs();
});
activityFromHub.client.displayLog = function () {
console.log('Hub Started');
FetchLogs();
}
function FetchLogs() {
$.ajax({
type: 'GET',
url: '/Home/GetLogs',
datatype: 'json',
success: function (data) {
$("#logs tr").remove();
data = $.parseJSON(data);
if (data.length > 0) {
.... do some append here
}
},
error: function (error) {
alert("error");
}
});
}
});
</script>
ActivityHelper.cs
static readonly string connString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
public static class ActivityHelper
{
public static string GetActivityLogs()
{
string sqlCommand = #"my select query here";
try
{
var messages = new List<ActivityLog>();
using(var connection = new SqlConnection(connString))
{
connection.Open();
using (SqlConnection con = new SqlConnection(connString))
{
SqlCommand cmd = new SqlCommand(sqlCommand, con);
if(con.State != System.Data.ConnectionState.Open)
{
con.Open();
}
cmd.Notification = null;
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
var reader = cmd.ExecuteReader();
while (reader.Read())
{
messages.Add(item: new ActivityLog
{
Activity = reader["Activity"] != DBNull.Value ? (string)reader["Activity"] : "",
LogDate = (DateTime)reader["LogDate"]
});
}
}
}
var jsonSerialiser = new JavaScriptSerializer();
var json = jsonSerialiser.Serialize(messages);
return json;
}
catch(Exception ex)
{
throw;
}
}
public static void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
SqlDependency dependency = sender as SqlDependency;
dependency.OnChange -= dependency_OnChange;
var activityHub = GlobalHost.ConnectionManager.GetHubContext<ActivityHub>();
GetActivityLogs();
}
}
}
FIRST METHOD
First Solution change your javascript code like this. If this not works move to the second method:
$(function () {
var activityFromHub = $.connection.ActivityHub;
$.connection.hub.start().done(function () {
FetchLogs();
});
activityFromHub.client.displayLog = function () {
console.log('Hub Started');
FetchLogs();
}
});
SECOND METHOD:
Each client connecting to a hub passes a unique connection id. You can retrieve this value in the Context.ConnectionId property of the hub context. And i found there is nothing happening like this. You may try this solution.
I think the simplest solution for your question is to use groups.
http://www.asp.net/signalr/overview/guide-to-the-api/working-with-groups
Your hub class would contain methods to join a group:
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task LeaveGroup(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
and your hub will be look like this:
public static void StartLogging(string groupName)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<ActivityHub>();
context.Clients.Group(groupName).displayLog();
//calls the signalR client part to execute javascript method
//context.Clients.All.displayLog();
}
And change your javascript as like this:
$(function () {
var activityFromHub = $.connection.ActivityHub;
$.connection.hub.start().done(function () {
activityFromHub.server.joinGroup("Group1");
activityFromHub.server.StartLogging("Group1");
FetchLogs();
});
activityFromHub.client.displayLog = function () {
console.log('Hub Started');
FetchLogs();
}
});
I hope this will resolve your issue. If you are still facing issue. Please leave comments. Thank you.

How to live notification in MVC with SignalR?

I'm trying to make live notification using signalR. My project is running on localhost. But I don't see my notification when I set webconfig server-side. (although I did it with signalR)
When I run the 'internet' part of Chrome 's check item, I see that the request does not fall. how do I make this problem?
ajax code;
function updateNotification() {
$('#notiContent').empty();
$('#notiContent').append($('<li>Yükleniyor...</li>'));
$.ajax({
type: 'GET',
datatype : JSON,
contentType: 'application/json; charset=utf-8',
url: '/notification/GetNotificationFlows',
success: function (response) {
$('#notiContent').empty();
if (response.length == 0) {
$('#notiContent').append($('<li>Data yok..</li>'));
}
$.each(response, function (index, value) {
$('#notiContent').append($('<li>Yeni kişi : ' + value.flowName + ' (' + value.flowPhone + ') eklendi.</li>'));
});
},
error: function (error) {
console.log(error);
}
})
}
Global.asax;
string con = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
SqlDependency.Start(con);
}
protected void Session_Start(object sender, EventArgs e)
{
NotificationComponent NC = new NotificationComponent();
var currentTime = DateTime.Now;
HttpContext.Current.Session["LastUpdated"] = currentTime;
NC.RegisterNotification(currentTime);
}
protected void Application_End()
{
//here we will stop Sql Dependency
SqlDependency.Stop(con);
}
}
Notification component
public void RegisterNotification(DateTime currentTime)
{
string conStr = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
string sqlCommand = #"SELECT [flowId],[flowName],[flowEMail],[flowPhone],[kaynakId] from [dbo].[flow] where [createDate] > #createDate";
//you can notice here I have added table name like this [dbo].[Contacts] with [dbo], its mendatory when you use Sql Dependency
using (SqlConnection con = new SqlConnection(conStr))
{
SqlCommand cmd = new SqlCommand(sqlCommand, con);
cmd.Parameters.AddWithValue("#createDate", currentTime);
if (con.State != System.Data.ConnectionState.Open)
{
con.Open();
}
cmd.Notification = null;
SqlDependency sqlDep = new SqlDependency(cmd);
sqlDep.OnChange += sqlDep_OnChange;
//we must have to execute the command here
using (SqlDataReader reader = cmd.ExecuteReader())
{
// nothing need to add here now
}
}
}
void sqlDep_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
SqlDependency sqlDep = sender as SqlDependency;
sqlDep.OnChange -= sqlDep_OnChange;
//from here we will send notification message to client
var notificationHub = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
notificationHub.Clients.All.notify("eklendi.");
//re-register notification
RegisterNotification(DateTime.Now);
}
}
public List<flow> GetFlows(DateTime afterDate)
{
using (smartCMSEntities dc = new smartCMSEntities())
{
return dc.flow.Where(a => a.createDate > afterDate).OrderByDescending(a => a.createDate).ToList();
}
}
Notification Controller
public JsonResult GetNotificationFlows()
{
var notificationRegisterTime = Session["LastUpdated"] != null ? Convert.ToDateTime(Session["LastUpdated"]) : DateTime.Now;
NotificationComponent NC = new NotificationComponent();
var list = NC.GetFlows(notificationRegisterTime);
Session["LastUpdate"] = DateTime.Now;
return new JsonResult { Data = list, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
Notification Hub
public class NotificationHub : Hub
{
//public void Hello()
//{
// Clients.All.hello();
//}
}
SQL (for sql dependency)
ALTER DATABASE [db_name] SET ENABLE_BROKER with rollback immediate;
I had the same problem you need to create your function inside your Hub.
Let say
public class NotificationHub : Hub
{
public static void Send()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
context.Clients.All.displayStatus();
}
}
And call it in your html
function updateNotificationCount() {
$('span.count').show();
var count = 0;
count = parseInt($('span.count').html()) || 0;
count++;
$('span.noti').css("color", "white");
// $('span.count').css({ "background-color": "red", "color": "white" });
$('span.count').html(count);
}
// signalr js code for start hub and send receive notification
var hub = $.connection.notificationHub;
// Declare a function on the hub hub so the server can invoke it
hub.client.displayStatus = function () {
updateNotificationCount();
};
// Start the connection
$.connection.hub.start();

JWT Authorization in .NET Core -- 401 Error

I am trying to implement JWT authentication between a simple UI and Web API. Both are .NET Core 2.0 and I'm using Ajax to call the API functions. I am able to login with no problem whatsoever and it passes the Bearer token back to me; however, when I decorate my SaveProduct method with Authorize, call it with Ajax, and pass the token, it returns 401 Unauthorized. As soon as I remove the Authorize it works fine. I've worked for days to figure out what I'm missing and have created several iterations of my apps to see if that would help, but have not been able to figure it out. I've scoured the web and tried numerous suggestions, but as yet to no avail.
I would appreciate any insights you might have. Thanks in advance!
Here is my code:
WEB API -- STARTUP
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace SportsStoreAngAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins",
builder => builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.Build());
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superdooperultrasafeKey#999")),
RequireSignedTokens = false,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "http://localhost:3700",
ValidAudience = "http://localhost:3700"
};
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("AllowAllOrigins");
app.UseStatusCodePages();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
}
WEB API -- LOGIN CONTROLLER
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SportsStoreAngAPI.Models;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SportsStoreAngAPI.Controllers
{
[Route("[controller]")]
public class LoginController : Controller
{
[HttpPost]
public JsonResult Login([FromBody] LoginModel user)
{
LoginReturnModel l = new LoginReturnModel();
if (user == null)
{
l.Success = false;
l.Token = null;
}
if (user.Name == "admin" && user.Password == "secret")
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey#345"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokenOptions = new JwtSecurityToken(
//issuer: "http://localhost:3700",
//audience: "http://localhost:3700",
//claims: new List<Claim>(),
expires: DateTime.Now.AddDays(5),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
l.Success = true;
l.Token = tokenString;
}
else
{
l.Success = false;
l.Token = null;
}
JsonResult jR = new JsonResult(l);
return jR;
}
}
}
WEB API - PRODUCTS CONTROLLER
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SportsStoreAngAPI.Models;
using System.Collections.Generic;
namespace SportsStoreAngAPI.Controllers
{
[Produces("application/json")]
[Route("[controller]")]
public class ProductsController : Controller
{
[HttpGet]
public IEnumerable<Product> GetProducts()
{
List<Product> p = new List<Product>();
return p;
}
[HttpPost, Authorize]
public Product SaveProduct([FromBody]Product p)
{
return p;
}
}
}
FRONT END UI
#{
ViewData["Title"] = "Home Page";
}
<button class="btn btn-primary" type="button" onclick="loginAPI()">Login</button>
<div name="puthere" id="puthere"></div>
<button class="btn btn-primary" type="button" onclick="postNewRecord()">Post New Record</button>
<div name="puthere2" id="puthere2"></div>
<script>
$(document).ready(function () {
});
var token = '';
var loginAPI = function () {
var myData =
{
Name: 'admin',
Password: 'secret'
};
var myDataString = JSON.stringify(myData);
$.ajax({
type: 'POST',
url: 'http://localhost:3700/login',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: myDataString,
success: function (results) {
$('#puthere').empty();
var html = '';
$.each(results, function (index, value) {
html = html + '<p>' + index + ' : ' + value + '</p>';
if (index == 'token') {
token = value;
};
});
$('#puthere').append(html);
},
error: function (xhr, textStatus, error) {
alert(Error);
alert(xhr.statusText);
alert(textStatus);
alert(error);
}
});
};
var postNewRecord = function () {
var myData =
{
Id: '0',
Name: 'Soccer Ball',
Category: 'Sports',
Description: 'Round ball for playing the beautiful game',
Price: '13.75'
};
var myDataString = JSON.stringify(myData);
$.ajax({
type: 'POST',
url: 'http://localhost:3700/products',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: myDataString,
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', token)
},
success: function() {
alert('Saved successfully!');
},
error: function () {
alert('Something went very wrong!');
}
});
};
</script>
EDIT: In your Startup-Class you have defined a different key than the one you are using when you generate your token. They also have to be the same. I'd suggest you to read a little about JWT and how it works here.
So first thing I noticed is that you removed the issuer from the token you generate in the back-end. You have to define them because you also set them up in your Startup-Class.
Secondly your ajax request header must be set like this:
xhr.setRequestHeader('Authorization', 'Bearer ' + token)
You have to define the authorization-type within the headers-value.

How to update Chosen dropdownlist's items(cascade dropdownlist)?

I use Chosen plugin. I want to refresh Category (Chosen) dropdownlist, when change Section dropdownlist.
Here is view:
#model CategoryInputModel
#Html.DropDownListFor(model => model.SectionId, ViewBag.SectionList as IEnumerable<SelectListItem>, new { id = "SectionId" })
#Html.ListBoxFor(model => model.CategoryIdSet, ViewBag.AllCategoryList as MultiSelectList
, new
{
#class = "chzn-select",
data_placeholder = "Choose Categories...",
#style = "width : 500px;",
id = "CategoryIdSet"
})
CategoryInputModel is like:
public class CategoryInputModel
{
public string Name { get; set; }
public int SectionId{ get; set; }
public List<int> CategoryIdSet{ get; set; }
}
I can create cascade dropdownlist for simple lists, but could not create it for multiple select. I tried this :
<script type="text/javascript">
$(function () {
$("#SectionId").change(
function () {
loadLevelTwo(this);
});
loadLevelTwo($("#SectionId"));
});
function loadLevelTwo(selectList) {
var selectedId = $(selectList).val();
$.ajax(
{
url: "#Url.Action("GetCategoriesBySectionId", "Project")",
type: "GET",
data: { id: selectedId },
success: function (data) {
$("#CategoryIdSet").html($(data).html());
},
error: function (xhr) {
alert("Something went wrong, please try again");
}
});
}
</script>
In controller:
public ActionResult GetCategoriesBySectionId(int id)
{
var result = MyService.GetCategoriesBySectionId(id);
// **its problem to create partial view that return chosen dropdownlist I need**
}
How can I create cascade Chosen dropdownlist?
I think you need to add a little more to your ajax callback. I replaced success method with done. Try this, it works for me:
function loadLevelTwo(selectList) {
var selectedId = $(selectList).val();
$.ajax(
{
url: "#Url.Action("GetCategoriesBySectionId", "Project")",
type: "GET",
data: { id: selectedId },
error: function (xhr) {
alert("Something went wrong, please try again");
}
}).done(function (data) {
$("#CategoryIdSet").children().each(function (index, option) {
$(option).remove();
});
//blank option
var items = "<option selected value=\"\"></option>";
$.each(data,
function () {
items += "<option value=\"" + this[0] + "\">" + this[1] + "</option>";
});
$("#CategoryIdSet").html(items)
$("#CategoryIdSet").trigger("liszt:updated");
$("#CategoryIdSet").change();
});
}
controller action could look like this:
public ActionResult GetCategoriesBySectionId(int id)
{
var result = MyService.GetCategoriesBySectionId(id);
//build JSON.
var modelDataStore = from m in result
select new[] { m.YourValueProperty.ToString(),
m.YourTextProperty };
return Json(modelDataStore, JsonRequestBehavior.AllowGet);
}

Resources