PUSH on event "DB Data Changes" using SignalR - c#

All right I followed the chat application demo just fine, a couple of other tuts as well, but all of them combined doesn't answer or due to my lack of intelligence to derive the answer I want. The scenario is as follows
I am building a website with
MVC 4
.NET Framework 4.0
IIS 7.5
ASP.NET 4
SQL Server 2008 R2
that is to accompany a desktop app, i.e. my web application and the desktop app will be interacting with the same DB. I want to add an AMS (Acess Management System) to the website and the desktop application, so that Users access rights to any function inside the apps can be managed finely grained. I want the changes to be PUSHED to the wesite in realtime.
For Example: A manager from the desktop app revokes the rights of the clerks to view sales reports, and a couple of clerks were online on the website, so I want those changes to be PUSHED to the website as they are made.
Now currently I am storing the rights of the users at login, so that with the increasing number of users wont cause the performance of the website to degrade quickly, now I know that when checking for every action either allowed or not, I can make a round trip to the DB and check for the condition, but as I said earlier, that would degrade the performance.
Now I want to PUSH the rights of the users to the website if there are some changes, either from the website itself or the desktop app, I looked into SignalR, and I found a solution here on SO (This and This), but I don't want a clock ticker running continuously and then broadcasting the changes to all connected clients. And the user that is being changed, maybe connected to the website or maybe not. Please can someone point me in the right direction

I have spent much time on trying to find a solution for this, and the simplest I've found, using SignalR, is to use Hubs as a gateway to your Repository/API:
So, here's how the project would be set up:
ASP.NET MVC Controllers' Actions dish out entire pages.
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
The View should be wrapped in a Layout that loads a Knockout MVVM. The View then initializes the part of the MVVMs that need to be used (as in, lump all your MVVM script in one file, and the View initializes the SignalR connections, to avoid needless connections (the code below has the Script initializing itself)). The View also has KnockOut bindings attached to it.
MVVM:
function AddressViewModel(rid, nick, line1, line2, city, state, zip)
{
<!-- Modifiable Properties should be observable. This will allow Hub updates to flow to the View -->
var self = this;
self.rid = rid;
self.nick = ko.observable(nick);
self.line1 = ko.observable(line1);
self.line2 = ko.observable(line2);
self.city = ko.observable(city);
self.state = ko.observable(new StateViewModel(state.RID, state.Title, state.Abbreviation));
self.zip = ko.observable(zip);
}
function StateViewModel(rid, title, abbreviation)
{
<!-- States are a stagnant list. These will not be updated -->
var self = this;
self.rid = rid;
self.title = title;
self.abbreviation = abbreviation;
}
var Page = new function()
{
//Page holds all Page's View Models. The init function can be modified to start only certain hubs.
var page = this;
page.init = function()
{
page.Account.init();
}
page.Account = new function ()
{
//Account holds account-specific information. Should only be retrieved on an encrypted, secure, and authorized connection.
account.init = function()
{
account.Addresses.init();
}
//Addresses manages the calls to Hubs and their callbacks to modify local content.
account.Addresses = new function ()
{
//Connect to the hub, and create an observable list.
var addresses = this;
addresses.hub = $.connection.accountAddressHub;
addresses.list = ko.observableArray([]);
//Called on initial load. This calls the Index() function on the Hub.
addresses.init = function ()
{
addresses.hub.server.index();
}
//displayMode allows for dynamic changing of the template.
addresses.displayMode = ko.computed(function ()
{
return 'Address';
});
//Empty allows to prompt user instead of just showing a blank screen.
addresses.empty = ko.computed(function ()
{
if (addresses.list().length == 0)
{
return true;
}
else
{
return false;
}
});
//During initial load, unless if MVC provides the information with the View, the list will be empty until the first SignalR callback. This allows us to prompt the user we're still loading.
addresses.loading = ko.observable(true);
//The Hub's Index function ought to reach indexBack with a list of addresses. The addresses are then mapped to the list, using the local AddressViewModel. Sets initial load to false, as we now have addresses.
addresses.hub.client.indexBack = function (addressList)
{
$.map(addressList, function (address)
{
addresses.list.push(new AddressViewModel(address.RID, address.Nick, address.Line1, address.Line2, address.City, address.State, address.ZIP));
});
addresses.loading(false);
}
}
}
}
On Run Script (Place in Layout, Script File, or View, depending on needs or confirgurations per page)
$(function ()
{
//Configures what SignalR will do when starting, on receive, reconnected, reconnected, or disconnected.
$.connection.hub.starting(function ()
{
$('.updated').hide();
$('.updating').show();
});
$.connection.hub.received(function ()
{
$('.updating').hide();
$('.updated').show();
});
$.connection.hub.reconnecting(function ()
{
$('.updated').hide();
$('.updating').show();
});
$.connection.hub.reconnected(function ()
{
$('.updating').hide();
$('.updated').show();
});
//This will keep attempt to reconnect - the beauty of this, if the user unplugs the internet with page loaded, and then plugs in, the client reconnects automatically. However, the client would probably not receive any backlog - I haven't test that.
$.connection.hub.disconnected(function ()
{
setTimeout(function ()
{
$.connection.hub.start();
}, 5000); // Restart connection after 5 seconds.
});
//Apply knockout bindings, using the Page function from above.
ko.applyBindings(Page);
//Start the connection.
$.connection.hub.start(function ()
{
}).done(function ()
{
//If successfully connected, call the init functions, which propagate through the script to connect to all the necessary hubs.
console.log('Connected to Server!');
Page.init();
})
.fail(function ()
{
console.log('Could not Connect!');
});;
});
LayOut:
<!DOCTYPE html>
<html>
<head>
. . .
#Styles.Render( "~/Content/css" )
<!-- Load jQuery, KnockOut, and your MVVM scripts. -->
#Scripts.Render( "~/bundles/jquery" )
<script src="~/signalr/hubs"></script>
. . .
</head>
<body id="body" data-spy="scroll" data-target="#sidenav">
. . .
<div id="wrap">
<div class="container">
#RenderBody()
</div>
</div>
#{ Html.RenderPartial( "_Foot" ); }
</body>
</html>
View (Index):
#{
ViewBag.Title = "My Account";
}
<div>
#{
Html.RenderPartial( "_AddressesWrapper" );
}
</div>
_AddressesWrapper:
<div data-bind="with: Page.Account.Addresses">
#{
Html.RenderPartial( "_Address" );
}
<div id="Addresses" class="subcontainer">
<div class="subheader">
<div class="subtitle">
<h2>
<span class="glyphicon glyphicon-home">
</span>
Addresses
</h2>
</div>
</div>
<div id="AddressesContent" class="subcontent">
<div class="row panel panel-primary">
<!-- Check to see if content is empty. If empty, content may still be loading.-->
<div data-bind="if: Page.Account.Addresses.empty">
<!-- Content is empty. Check if content is still initially loading -->
<div data-bind="if:Page.Account.Addresses.loading">
<!-- Content is still in the initial load. Tell Client. -->
<div class="well well-lg">
<p class="text-center">
<img src="#Url.Content("~/Content/Images/ajax-loader.gif")" width="50px" height="50px" />
<strong>We are updating your Addresses.</strong> This should only take a moment.
</p>
</div>
</div>
<div data-bind="ifnot:Page.Account.Addresses.loading">
<!-- Else, if not loading, the Client has no addresses. Tell Client. -->
<div class="well well-lg">
<p class="text-center">
<strong>You have no Addresses.</strong> If you add an Addresses, you can view, edit, and delete it here.
</p>
</div>
</div>
</div>
<!-- Addresses is not empty -->
<div data-bind="ifnot: Page.Account.Addresses.empty">
<!-- We have content to display. Bind the list with a template in the Partial View we loaded earlier -->
<div data-bind="template: { name: Page.Account.Addresses.displayMode, foreach: Page.Account.Addresses.list }">
</div>
</div>
</div>
</div>
</div>
</div>
_Address:
<script type="text/html" id="Address">
<div class="col-lg-3 col-xs-6 col-sm-4 well well-sm">
<address>
<strong data-bind="text: nick"></strong><br>
<span data-bind="text: line1"></span><br>
<span data-bind="if: line2 == null">
<span data-bind="text: line2"></span><br>
</span>
<span data-bind="text: city"></span>, <span data-bind=" text: state().abbreviation"></span> <span data-bind="text: zip"></span>
</address>
</div>
</script>
The KnockOut script interchanges with a SignalR Hub. The Hub receives the call, checks the authorization, if necessary, and passes the call to the proper repository or straight to WebAPI 2 (this example). The SignalR Hub action then takes the results of the API exchange and determines which function to call, and what data to pass.
public class AccountAddressHub : AccountObjectHub
{
public override async Task Index()
{
//Connect to Internal API - Must be done within action.
using( AddressController api = new AddressController(await this.Account()) )
{
//Make Call to API to Get Addresses:
var addresses = api.Get();
//Return the list only to Connecting ID.
Clients.Client( Context.ConnectionId ).indexBack( addresses );
//Or, return to a list of specific Connection Ids - can also return to all Clients, instead of adding a parameter.
Clients.Clients( ( await this.ConnectionIds() ).ToList() ).postBack( Address );
}
}
}
The API Controller checks data integrity and sends a callback to the same SignalR Hub action.
public class AddressController
: AccountObjectController
{
...
// GET api/Address
public ICollection<Address> Get()
{
//This returns back the calling Hub action.
return Account.Addresses;
}
...
}
Your .NET application will need to use the same functions as your javascript-ran site. This will allow a modification from any client to then propagate to however many clients are needed (that single client who just loaded, as in this example, or to broadcast to everyone, or anywhere in between)
The end result is that the Hub receives changes/calls, calls the API, the API verifies the data and returns it back to the Hub. The Hub can then update all clients. You then successfully have real-time database changes and real-time client changes. The only catch is any change outside of this system will require the clients to refresh, which means all client calls, especially changes, must go through Hubs.
If you need more examples, I would be happy to show some. Obviously, security measures should be taken, and the code here is obviously only a small example.

If you want use signalr I think you should use push server.But you can use another way and send a request to the api and the api should know about db change.
For push server you can also see this.

There are some considerations that might help.
1- Since you are playing with the Access rights , so i would say that , you must check the access right at run time each time , user wants to access certain secured functionality , yes this will have some degradation in performance but ensure you the tighter granular security.
2- For sending periodic changes , i would say that , you can use Timer available in .Net and trigger changes at a certain interval.
3- I still don't like the idea of sending security related information to the client (thin) because anybody with basic knowledge of JavaScript and Html can change the security by running your site in debug mode or through some automated tools like Fiddler.

I've made a library that proxies between a server side eventaggregator / service bus. It makes it alot easier to stream line the events being sent to clients. Take a look at the demo project here
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/tree/master/SignalR.EventAggregatorProxy.Demo.MVC4
Open the demo .sln and there is both a .NET client (WPF) and a javascript client example
Install using nuget
Install-Package SignalR.EventAggregatorProxy
wiki
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/wiki

Related

MVC - anti-forgery token error

I've been brought onto my first MVC and C# project so I appreciate any guidance.
I've created a new feature that checks to see if user has had security training when they log in. If they haven't, the user is directed to a training page where they simply agree/disagree to the rules. If the user agrees, login is completed. If user disagrees, he/she is logged off.
The issue that I have is that when I select the Agree/Disagree button in the training view, I get the following
It should route me to the homepage or logout the user.
Controller
public ActionResult UserSecurityTraining(int ID, string returnUrl)
{
// check if user already has taken training (e.g., is UserInfoID in UserSecurityTrainings table)
var accountUser = db.UserSecurityTraining.Where(x => x.UserInfoID == ID).Count();
// If user ID is not in UserSecurityTraining table...
if (accountUser == 0)
{
// prompt security training for user
return View("UserSecurityTraining");
}
// If user in UserSecurityTraining table...
if (accountUser > 0)
{
return RedirectToLocal(returnUrl);
}
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UserSecurityTrainingConfirm(FormCollection form, UserSecurityTraining model)
{
if (ModelState.IsValid)
{
if (form["accept"] != null)
{
try
{
// if success button selected
//UserSecurityTraining user = db.UserSecurityTraining.Find(); //Create model object
//var user = new UserSecurityTraining { ID = 1, UserInfoID = 1, CreatedDt = 1 };
logger.Info("User has successfully completed training" + model.UserInfoID);
model.CreatedDt = DateTime.Now;
db.SaveChanges();
//return RedirectToAction("ChangePassword", "Manage");
}
catch (Exception e)
{
throw e;
}
return View("SecurityTrainingSuccess");
}
if(form["reject"] != null)
{
return RedirectToAction("Logoff", "Account");
}
}
return View("UserSecurityTraining");
}
View
#model ECHO.Models.UserSecurityTraining
#{
ViewBag.Title = "Security Training";
Layout = "~/Views/Shared/_LayoutNoSidebar.cshtml";
}
<!--<script src="~/Scripts/RequestAccess.js"></script>-->
<div class="container body-content">
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-md-8">
#using (Html.BeginForm("UserSecurityTrainingConfirm", "Account", FormMethod.Post, new { role = "form" }))
{
<fieldset>
#Html.AntiForgeryToken()
Please view the following security training slides:<br><br>
[INSERT LINK TO SLIDES]<br><br>
Do you attest that you viewed, understood, and promise to follow the guidelines outlined in the security training?<br><br>
<input type="submit" id="accept" class="btn btn-default" value="Accept" />
<input type="submit" id="reject" class="btn btn-default" value="Reject" />
</fieldset>
}
</div><!--end col-md-8-->
</div><!--end row-->
</div><!-- end container -->
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
I don't think you've provided enough of your code to properly diagnose this particular error. In general, this anti-forgery exception is due to a change in authentication status. When you call #Html.AntiForgeryToken() on a page, a cookie is set as well in the response with the token. Importantly, if the user is authenticated, then the user's identity is used to compose that token. Then, if that user's authentication status changes between when the cookie is set and when the form is posted to an action that validates that token, the token will no longer match. This can either be the user being authenticated after the cookie set or being logged out after the cookie is set. In other words, if the user is anonymous when the page loads, but becomes logged in before submitting the form, then it will still fail.
Again, I'm not seeing any code here that would obviously give rise to that situation, but we also don't have the full picture in terms of how the user is being logged in an taken to this view in the first place.
That said, there are some very clear errors that may or may not be causing this particular issue, but definitely will cause an issue at some point. First, your buttons do not have name attributes. Based on your action code, it appears as if you believe the id attribute will be what appears in your FormCollection, but that is not the case. You need to add name="accept" and name="reject", respectively, to the buttons for your current code to function.
Second, on the user successfully accepting, you should redirect to an action that loads the SecurityTrainingSuccess view, rather than return that view directly. This part of the PRG (Post-Redirect-Get) pattern and ensures that the submit is not replayed. Any and all post actions should redirect on success.
Third, at least out of the box, LogOff is going to be a post action, which means you can't redirect to it. Redirects are always via GET. You can technically make LogOff respond to GET as well as or instead of POST, but that's an anti-pattern. Atomic actions should always be handled by POST (or a more appropriate verb like PUT, DELETE, etc.), but never GET.
Finally, though minor, the use of FormCollection, in general, is discouraged. For a simple form like this, you can literally just bind your post as params:
public ActionResult UserSecurityTrainingConfirm(string accept, string reject, ...)
However, then it'd probably be more logical and foolproof to introduce a single boolean like:
public ActionResult UserSecurityTrainingConfirm(bool accepted, ...)
Then, your buttons could simply be:
<button type="submit" name="accepted" value="true" class="btn btn-default">Accept</button>
<button type="submit" name="accepted" value="false" class="btn btn-default">Reject</button>
This essentially makes them like radios. The one that is clicked submits its value, so the accepted param, then, will be true or false accordingly. Also, notice that I switched you to true button elements. The use of input for buttons is a bad practice, especially when you actually need it to submit a value, since the value and the display are inherently tied together. With a button element, you can post whatever you want and still have it labeled with whatever text you want, independently.

Braintree: Generate Client Token in ASP.NET MVC 4.5

I'm having some issues integrating Braintree, as well as understanding the concept of how the transaction takes place.
Here's how i currently understand Braintree:
Server Generates ClientToken -> Integrates into html/js -> User receives payment form and sends data to Braintree -> Nonce is sent to Server -> Server sends Transaction to Braintree
Is this correct?
I'm currently on step 1, trying to generate a client token, and i'm getting a NullReferenceException:
public ActionResult Payment(EditContainerViewModel newEdit)
{
//generate client token
newEdit.PaymentInfo.ClientToken = PaymentConstants.Gateway.ClientToken.generate();
return View(newEdit);
}
And heres the Gateway declaration:
public static class PaymentConstants
{
public static BraintreeGateway Gateway = new BraintreeGateway
{
Environment = Braintree.Environment.SANDBOX,
MerchantId = "id",
PublicKey = "publickey",
PrivateKey = "privatekey"
};
}
Heres my view:
#model Sandbox.Models.EditContainerViewModel
<h2>Payment</h2>
<form id="checkout" method="post" action="/checkout">
<div id="payment-form"></div>
<input type="submit" value="Pay $10">
</form>
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script>
// We generated a client token for you so you can test out this code
// immediately. In a production-ready integration, you will need to
// generate a client token on your server (see section below).
var clientToken = "#Html.Raw(Model.PaymentInfo.ClientToken)";
braintree.setup(clientToken, "dropin", {
container: "payment-form"
});
</script>
I'd really appreciate any insight on this topic, especially as i found that the provided ASP.NET examples didn't show the token generation stage.
Thank you!
As said in the comment, i was far too focused on Braintree that i made an extremely beginner error. This serves as a great reminder that you can find the answer by just taking a step back and looking at the error from a new perspective. :)

Is sending Session from javascript safe?

Well, this is a bit weird i think to ask this question, because i am not sure if that's the place to ask that.
OK, into the question..
I have this code
<script>
var session = "<%= Session["User"]%>";
</script>
So, i was thinking, is that safe? let me tell you what i mean..
I have a web api which you can get the name, last name, age and everything about the user with his Session, can i send this web api this session and use it?
Is that a safe thing to do ? in matter of securiy? if not, is there any better way?
EDIT 1:
What am i trying to aaccomplish? simple, i will store the UserId in the session, the UserId will Guid, when the user is loogin in the javascript can send post to an API server to get info, the API will send the UserId from the session.
Is That ok?
Workflow that you describe looks fine. For me it seems safe to use some ID to get more information about some user, especially if this is supposed to be an API, at least, Facebook API uses such principle not being afraid of some hackers :)
My main concern here is the coding style when you try to mix code and view which is not good. If you really need to share some information between client and server sides then I would go with one of these options.
Option # 1 - Cookies
What is the difference between a Session and a Cookie?
You can keep some simple information in a cookie and get it this way :
Client : $.cookie('ID')
Server : Response.Cookies["ID"]
In this case there is no need to put in a mess your client side JS with C# code and cookies will be saved on users PC which means that nobody will see them except him.
Option # 2 - Templates
Server : put all needed information into hidden form or ViewState
Client : take information from hidden form using HTML selectors
Straight answer :
In general, if you worry only about safety then it is fine to use this code, it should not break security of your site.
Although, personally I do not like this approach because :
you will mix code and view, MVC was created to split them
it is not clear where exactly in your view you will put this code and thus it is not clear how you are going to check that this variable was initialized
it may happen that you will put there some value that will break JS syntax and will cause JS error
In my personal opinion, I would replace it with one of the mentioned options.
Option 1 - MVC + JQuery + Cookie Example
public ActionResult Index()
{
string demo = Request.QueryString["MyNameSpace.ID"]; // get value from client
Response.Cookies["MyNameSpace.ID"].Value = "server"; // change value in response
return View();
}
Then in your JS file :
$(document).ready(function() { // make sure server rendered page
var ID = $.cookie('MyNameSpace.ID'); // get cookie value from server
$.cookie('MyNameSpace.ID', 'client'); // update, on the next request server will get it
});
Option 2 - MVC + JQuery + Templates Example
public class OptionsModel // View Model
{
public string ID { get; set; }
public string User { get; set; }
}
public ActionResult Index() // Controller
{
OptionsModel options = new OptionsModel();
options.ID = "server";
return View(options);
}
Your view :
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<OptionsModel>" %>
<%=Html.HiddenFor(m => Model.ID, new { #class = "MyNameSpace:ID" })%>
<%=Html.HiddenFor(m => Model.User, new { #class = "MyNameSpace:User" })%>
Then in your JS file :
$(document).ready(function() { // make sure server rendered page
var options = $('[class^=MyNameSpace]') // get values from hidden fields
options[0] = 'client'; // update data
$.ajax({ data : options }); // create handler to send data back to server
});
Examples for Web Forms do not differ significantly.
The code you have posted will be rendered on the page as so when it hits the client (assuming you are using ASP.NET
<script>
var session = "John Smith";
</script>
This is due to the use of the server side scripting tags <%= %> (https://technet.microsoft.com/en-us/library/cc961121.aspx)
As a note its probably not the best thing in the world to fully expose the session to javascript if that is your intention. At the end of the day it depends what you are storing in there and using it for (but ASP.NET will also use it for certain things) but exposing it just opens another area for someone to attack.
http://www.owasp.org is a great place to learn more about securing your website.

MVC PagedList sending weird requests to server

I am using MVC with PagedList in order to have a big table divided into multiple pages.
Now, in the web browser, this is what I see:
http://localhost:49370/Home/Pending?page=2
Which makes sense. However, when sending a request to the server, this is what the server receives: http://localhost:49370/Home/WhereAmI?_=1429091783507
This is a huge mess, and in turn it makes it impossible to redirect the user to specific pages in the list because I don't know what is the page the user is currently viewing !
Controller code:
public ActionResult Pending(int? page)
{
//I have a ViewModel, which is MaterialRequestModel
IEnumerable<MaterialRequestModel> model = DB.GATE_MaterialRequest
.Select(req => new MaterialRequestModel(req))
.ToList();
int pageNum = page ?? 1;
return View(model.ToPagedList(pageNum, ENTRIES_PER_PAGE));
}
View code:
#model IEnumerable<MaterialRequestModel>
<table>
//table stfuff
</table>
<div style="display: block;text-align: center">
#Html.PagedListPager((PagedList.IPagedList<MaterialRequestModel>)Model, page => Url.Action("Pending", new { page }), PagedListRenderOptions.ClassicPlusFirstAndLast)
</div>
Is this a limitation of MVC PagedList? Or am I missing something?
It happens that PagedList does not send this type of information to the server. Instead, if you want to know which page is being looked at, you have to use a custom model that has that information, and if you want to make a request usign ajax (the original objective here) you must using a special option:
#Html.PagedListPager(Model.requests, page => Url.Action("SearchTable", "Home",
new
{
employeesQuery = Model.query.employeesQuery, //your query here
pageNum = page
}
), PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing(new AjaxOptions() { HttpMethod = "GET", UpdateTargetId = "tableAndPaginationDiv", OnComplete = "initiatePendingTableDisplay" }))
Admittedly this solution is poor at best. You can only have 1 option of the entire list (so if you are already using other options somewhere else you can forget it) and you have no control whatsoever on the request made, so customization is really not an option here unless you feel like hooking the calls.
Anyway, this is how I fixed it, hopefully it will help someone in the future!

Response.StatusDescription not being sent from JsonResult to jQuery

Please keep in mind that I'm rather new to how MVC, Json, jQuery, etc. works, so bear with me. I've been doing Web Forms for the past 5 years...
I'm working on validating a form within a Modal popup that uses a JsonResult method for posting the form data to the server. I wish I could have just loaded a Partial View in that popup and be done with it, but that's not an option.
Anyway, I have some code that was working yesterday, but after I did a pull / push with Git, something went a bit wrong with my validation. I do some basic validation with regular JavaScript before I pass anything to the server (required fields, correct data types, etc.), but some things, like making sure the name the user types in is unique, require me to go all the way to the business logic.
After poking around the internet, I discovered that if you want jQuery to recognize an error from a JsonResult in an AJAX request, you must send along a HTTP Status Code that is of an erroneous nature. I'm fairly certain it can be any number in the 400's or 500's and it should work...and it does...to a point.
What I would do is set the Status Code and Status Description using Response.StatusCode and Response.StatusDescription, then return the model. The jQuery would recognize an error, then it would display the error message I set in the status description. It all worked great.
Today, it seems that the only thing that makes it from my JsonResult in my controller to my jQuery is the Status Code. I've traced through the c# and everything seems to be set correctly, but I just can't seem to extract that custom Status Description I set.
Here is the code I have:
The Modal Popup
<fieldset id="SmtpServer_QueueCreate_Div">
#Form.HiddenID("SMTPServerId")
<div class="editor-label">
#Html.LabelFor(model => model.ServerName)
<br />
<input type="text" class="textfield wide-box" id="ServerName" name="ServerName" title="The display name of the Server" />
<br />
<span id="ServerNameValidation" style="color:Red;" />
</div>
<div class="editor-label">
<span id="GeneralSMTPServerValidation" style="color:Red;" />
</div>
<br />
<p>
<button type="submit" class="button2" onclick="onEmail_SmtpServerQueueCreateSubmit();">
Create SMTP Server</button>
<input id="btnCancelEmail_SmtpServerQueueCreate" type="button" value="Cancel" class="button"
onclick="Email_SmtpServerQueueCreateClose();" />
</p>
</fieldset>
The Controller
[HttpPost]
public virtual JsonResult _QueueCreate(string serverName)
{
Email_SmtpServerModel model = new Email_SmtpServerModel();
string errorMessage = "";
try
{
Email_SmtpServer dbESS = new Email_SmtpServer(ConnectionString);
model.SMTPServerId = System.Guid.NewGuid();
model.ServerName = serverName;
if (!dbESS.UniqueInsert(model, out errorMessage))
{
return Json(model);
}
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
Response.StatusCode = 500;
Response.StatusDescription = errorMessage;
return Json(model);
}
The jQuery Ajax Request
$.ajax({
type: 'POST',
data: { ServerName: serverName },
url: getBaseURL() + 'Email_SmtpServer/_QueueCreate/',
success: function (data) { onSuccess(data); },
error: function (xhr, status, error) {
$('#GeneralSMTPServerValidation').html(error);
}
});
Like I mentioned, yesterday, this was showing a nice message to the user informing them that the name they entered was not unique if it happened to exist. Now, all I'm getting is a "Internal Server Error" message...which is correct, as that's what I am sending along in my when I set my Status Code. However, like I mentioned, it no longer sees the custom Status Description I send along.
I've also tried setting it to some unused status code to see if that was the problem, but that simply shows nothing because it doesn't know what text to show.
Who knows? Maybe there's something now wrong with my code. Most likely it was a change made somewhere else, what that could be, I have no idea. Does anyone have any ideas on what could be going wrong?
If you need any more code, I'll try to provide it.
Thanks!
In your error callback, try using
xhr.statusText
Also, if you're using Visual Studio's webserver, you may not get the status text back.
http://forums.asp.net/post/4180034.aspx

Categories