Table of Contents
Introduction
In this article, I will share how to implement the warning banner and reusable Modal using ASP.NET MVC, jQuery, Web API, Bootstrap, AngularJS and AngularUI Bootstrap. Some people refer to a warning banner as a disclaimer banner, consent screen, splash screen, system acceptable use policy, log-on warning banner, login policy, Logon Banner, Logon Notice, etc... Basically, this is the page/notice that appears/displays when the user first visit or logon into the application. Here is the summary on what the reader can extract from this article.
- How to create warning banner using jQuery + Bootstrap
- How to create warning banner using AngularJS + AngularUI Bootstrap
- How to create a simple Web API
- How to call Web API from MVC Controller
- How to include anti-forgery token into the AJAX POST Request
- How to create reusable Modal using MVC partial view and jQuery + Bootstrap
- How to create reusable Modal using AngularJS Template + AngularUI Bootstrap
- How to create an Authorization filter
Figure 1 shows the Solution containing two projects, namely CommonApp and WarningBanner respectively. The CommonApp is a simple WebAPI project responsible for returning the warning banner content.
Figure 1
Warning Banner View
Listing 1 shows the Bootstrap modal markup in the WarningPage view. In this example, the content for the modal-body will be read from the ViewBag. You can change it to bind from the Model or hard-code the banner content onto the page depending on your requirement. The data-keyboard is set to "false" to prevent the user from closing the warning banner by hitting the ESC key on the keyboard.
Listing 1
- @{ Html.RenderPartial("~/Views/Shared/_AntiforgeryToken.cshtml"); }
- div class="modal fade" id="myModal" role="dialog" data-keyboard="false" data-backdrop="static">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal">×</button>
- </div>
- @if (ViewBag.SystemWarningMessage != null)
- {
- <div class="modal-body"> @Html.Raw(ViewBag.SystemWarningMessage)</div>
- }
- <div class="modal-footer">
- <button type="button" class="btn btn-default" id="btnAcceptPolicy" data-dismiss="modal">OK</button>
- </div>
- </div>
- </div>
- </div>
Warning Banner View Action
On page load, the WarningPage action method will check if the user clicked on the OK button on the warning banner page. If yes, redirect the request to Home page else get the warning banner message and display it on the page.
Listing 2
- public ActionResult WarningPage()
- {
- if (Session["SessionOKClick"] != null)
- {
- return RedirectToAction("Index");
- }
- SetWarningBanner();
- return View();
- }
Shown in listing 3 is the code to set the warning banner content. In this example, the method is getting the content from the Web API through the HttpClient class. The code is very straight forward, however, we can make a few improvements here. The code can be modified to read the Web API URI from a configuration file and sanitize the JSON result before return it to the client. If Web API is not the preferred choice, then we can modify the code to read the content from the database/entity, configuration file or even hard-coded it. The purpose of the cache-control headers in this method is to prevent the browser back button from serving the content from the cache. For instance, if the user click on the browser back button after accepted the policy, the Warning Banner Modal will re-appear and prompt the user to accept the policy again.
Listing 3
- internal void SetWarningBanner()
- {
- Response.Cache.SetCacheability(HttpCacheability.NoCache);
- Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1));
- Response.Cache.SetNoStore();
-
- using (var client = new HttpClient())
- {
- client.BaseAddress = new Uri("http://localhost:47503");
- client.DefaultRequestHeaders.Accept.Clear();
- client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
- var response = client.GetAsync("api/warningbanner/1").Result;
- if (response.IsSuccessStatusCode)
- {
- string responseString = response.Content.ReadAsStringAsync().Result;
- JObject json = JObject.Parse(responseString);
-
- ViewBag.SystemWarningMessage = json["Content"].ToString();
- }
- }
- }
Warning Banner Web API
Listing 4 shows the contents of the Warning Banner web API controller. This class contains a WarningBanner object with some sample data in it and a Get method to retrieve the WarningBanner by id. Again, feel free to modify this code to read the data from the database/entity, configuration file and etc.
Listing 4
- public class WarningBannerController : ApiController
- {
-
- IList<WarningBanner> warningBanners = new List<WarningBanner>
- {
- new WarningBanner { Id = 1, Content = "<b> *** Warning Banner Content 1 ****</b>",
- LastUpdatedDate = DateTime.Now},
- new WarningBanner { Id = 2, Content = "<b> *** Warning Banner Content 2 ****</b>",
- LastUpdatedDate= DateTime.Now.AddDays(-30)}
- };
- public IHttpActionResult Get(int id)
- {
- var banner = warningBanners.FirstOrDefault((p) => p.Id == id);
- if (banner == null)
- {
- return NotFound();
- }
- return Ok(banner);
- }
- }
Warning Banner View OK button click
Listing 5 shows that the Ajax call will be triggered to post to Home/AcceptPolicy method on Ok button click. In this example, the post will include an anti-forgery token and key value pair data. if the request succeeds, the request will be redirected to the page that is initially being requested.
Listing 5
- <script type="text/javascript">
- $(window).load(function () {
- $('#myModal').modal('show');
- });
- jQuery(document).ready(function () {
- jQuery("#btnAcceptPolicy").click(function () {
- jQuery.ajax({
- type: "POST",
- url: '@Url.Content("~/Home/AcceptPolicy")',
- data: { rURL: 'someDummyValue' },
- beforeSend: function (xhr) { xhr.setRequestHeader('RequestVerificationToken', $("#antiForgeryToken").val()); },
- success: function (data) {
- window.location.href = data;
- }
- });
- });
- });
- </script>
Warning Banner OK button Action method
The AcceptPolicy action method is being decorated with HttpPost and AjaxValidateAntiForgeryToken attributes. The main purpose of this method is to tell the client what URL to redirect the request to, after user clicks on the Ok button. Refer to listing 6. You can be creative here also by adding additional logic to log the user response into the database. The method accepts one parameter, rURL, that variable is not being used by the code. The goal is to demonstrate that the AJAX post can pass a parameter to the action method. The purpose of SessionOKClick session variable is to keep track if user clicked on the Ok button. On the other hand, SessionReturnUrl session variable contain the URL of the Initial page request. The URL is being set at AuthorizeAttribute.OnAuthorization method which will be discussed in the next section.
Listing 6
- [HttpPost]
- [AjaxValidateAntiForgeryToken]
- public ActionResult AcceptPolicy(string rURL)
- {
- Session["SessionOKClick"] = "1";
- string decodedUrl = string.Empty;
-
- if (Session["SessionReturnUrl"] != null)
- {
- decodedUrl = Server.UrlDecode(Session["SessionReturnUrl"].ToString());
- }
- if (Url.IsLocalUrl(decodedUrl))
- {
- return Json(decodedUrl);
- }
- else
- {
- return Json(Url.Action("Index", "Home"));
- }
- }
RequireAcceptPolicy - Custom Authorization filter attribute
Listing 7 shows the custom authorization filter to check if the request needs to be redirected to the warning banner page before the action methods gets invoked by the authorized users. In this example, the OnAuthorization method contains only the logic to handle the warning banner policy. In reality, your application might have other logic to validate authorized user. We can append/merge the code in Listing 7 into your application authorization filter class. The PageToSkipPolicyNotification and PageToShowPolicyNotification methods allow to specify certain page to skip or show the warning banner page respectively. The OnAuthorization method will redirect the request to Warning page if
- The action is not from Ok button click (AcceptPolicy) and
- The user must accept the policy before viewing the requested page and
- The user has not click on the Ok button (AcceptPolicy) before
Recall earlier, the SessionOKClick session variable is being set at WarningPage AcceptPolicy Action method. The purpose of the SessionReturnUrl session variable is to hold the URL of the request. We can’t expect all the users to enter the web application through the /home page, some might enter from /home/contact page, /home/about page, and etc. Let said the user initially enter the web application from /home/contact page. After the user clicks on the Ok button (AcceptPolicy) on the warning page, the request will be redirected to the /home/contact page. If you want to see the warning banner with AngularJS, in the appSettings element of the web.config, set the WaningBannerAngular value to 1.
Listing 7
- public class RequireAcceptPolicy : AuthorizeAttribute, IAuthorizationFilter
- {
- internal static bool PageToSkipPolicyNotification(HttpContext ctx)
- {
- string[] pagesToExclude = { "/home/testwarningpage", "/home/warningpage"};
- string pageToCheck = ctx.Request.Path.ToString().ToLower().TrimEnd('/');
- return pagesToExclude.Contains(pageToCheck) ? true : false;
- }
- internal static bool PageToShowPolicyNotification(HttpContext ctx){ ….}
- public override void OnAuthorization(AuthorizationContext filterContext)
- {
-
- ….
-
- if (!filterContext.ActionDescriptor.ActionName.ToLower().Contains("acceptpolicy") && !PageToSkipPolicyNotification(HttpContext.Current) &&
- HttpContext.Current.Session["SessionOKClick"] == null)
- {
-
- HttpContext.Current.Session["SessionReturnUrl"] = filterContext.HttpContext.Request.Url.PathAndQuery;
-
- if (System.Configuration.ConfigurationManager.AppSettings["WaningBannerAngular"] == "1")
- {
- filterContext.Result = new RedirectResult("~/home/WarningPageNg");
- }
- else
- {
- filterContext.Result = new RedirectResult("~/home/WarningPage");
- }
- }
- }
- }
Filter Config
Listing 8 shows how to add the Custom Authorization filter attribute, RequireAcceptPolicy to the global filter collection.
Listing 8
- public class FilterConfig
- {
- public static void RegisterGlobalFilters(GlobalFilterCollection filters)
- {
- filters.Add(new RequireAcceptPolicy());
- }
- }
Ajax Validate Anti-Forgery Token
The Anti-Forgery Token code is from Passing Anti-Forgery Token to MVC Actions using Ajax Post . Instead of placing the client related code on every single page, the code is placed into a partial view, _AntiforgeryToken.cshtml. You can place the partial view that requires Anti-Forgery Token into a single page or Layout page. Shown in listing 9 is the contents of _AntiforgeryToken.cshtml. The partial view comprises of two hidden fields with the ID antiForgeryToken and antiForgeryTokenNg respectively. The hidden field hold the Anti-Forgery Token generated by the GetAntiForgeryToken method. The antiForgeryTokenNg hidden field is utilized by AngularJS.
Listing 9
- @functions{
- public string GetAntiForgeryToken()
- {
- string cookieToken, formToken;
- AntiForgery.GetTokens(null, out cookieToken, out formToken);
- return cookieToken + "," + formToken;
- }
- }
- <input type="hidden" id="antiForgeryToken" value="@GetAntiForgeryToken()" />
- <input id="antiForgeryTokenNg" data-ng-model="antiForgeryToken" type="hidden"
- data-ng-init="antiForgeryToken='@GetAntiForgeryToken()'" />
Listing 10 shows the jQuery AJAX post passing the Anti-Forgery Token to the Controller Action method. You can see the complete JavaScript from listing 5.
Listing 10
- beforeSend: function (xhr) { xhr.setRequestHeader('RequestVerificationToken', $("#antiForgeryToken").val()); }
Listing 11 briefly shows how the AngularJS passing the Anti-Forgery Token to the Controller Action method. Please download the source code because I might skip something in this section. On page load the Controller will read the token from the hidden field and then store it into an array variables namely items. When the users click on the Accept Policy/OK button, the AngularJS $http service will post to the server including the headers property. In this example the header property contain RequestVerificationToken parameter and the value from $scope.items[0]. The array in this example happens to have only one item that's why the position is 0.
Listing 11
-
- $scope.items = [$scope.antiForgeryToken];
-
- $http({
- method: 'POST',
- url: '/home/AcceptPolicy',
- params: Indata,
- headers: {
- 'RequestVerificationToken': $scope.items[0]
- }
- …
Warning Banner View using AngularJS
Listing 12 shows the AngulatJS and AngularUI Bootstrap modal markup in the WarningPageNg view. Like the WarningPage view, the content for the modal-body is from the ViewBag. You can change it to bind from the Model or hard-code the banner content onto the page depending on your requirement. The contents of the modal are encapsulated in the template under the script directive. The OK button click is routed to the ok() function and openModal() function will be call on page load.
Listing 12
- <div ng-controller="modalcontroller">
- <script type="text/ng-template" id="myModal.html">
- <div class="modal-header"></div>
- <div class="modal-body">
- @if (ViewBag.SystemWarningMessage != null)
- {
- <div class="modal-body"> @Html.Raw(ViewBag.SystemWarningMessage)</div>
- }
- </div>
- <div class="modal-footer">
- <button class="btn btn-default" type="button" ng-click="ok()">OK</button>
- </div>
- </script>
- @{ Html.RenderPartial("~/Views/Shared/_AntiforgeryToken.cshtml"); }
- <span ng-init="openModal()"></span>
- </div>
- @section scripts{
- <script src="~/ControllersNg/ModalController.js"></script>
- }
Shown in listing 13 is the contents of ModalController.js file. Initially, the code will create a new module namely TestAngularApp and declare a dependency on the ui.bootstrap module. Then the code will utilize the Config function to inject $httpProvider provider into the module configuration block in the application. The provider will allow the module to add headers of "X-Requested-With" type to the performed calls. This header is necessary to work well with the AjaxValidateAntiForgeryToken attribute and solve the error "required anti-forgery cookie \"__RequestVerificationToken\" is not present." You can see a list of AngularUI Modal open properties and definition here. The OK button click logics are very similar to listing 5. On button click, the page will post to home/AcceptPolicy method with Anti-Forgery token.
Listing 13
- var app = angular.module('TestAngularApp', ['ui.bootstrap']);
- app.config(['$httpProvider', function ($httpProvider) {
- $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
- }]);
- app.controller('modalcontroller', function ($scope, $uibModal) {
- $scope.animationsEnabled = true;
- $scope.openModal = function (size) {
- $scope.items = [$scope.antiForgeryToken];
- var ModalInstance = $uibModal.open({
- animation: $scope.animationsEnabled,
- templateUrl: 'myModal.html',
- controller: 'InstanceController',
- backdrop: false,
- resolve: {
- items: function () { return $scope.items; }
- }
- });
- };
- });
- app.controller('InstanceController', function ($scope, $uibModalInstance, $http, $window, items) {
- $scope.items = items;
- $scope.ok = function () {
- var Indata = { rURL: 'dummyTestParam' };
- $http({
- method: 'POST',
- url: '/home/AcceptPolicy',
- params: Indata,
- headers: {'RequestVerificationToken': $scope.items[0] }
- }).then(function successCallback(response) {
- $scope.status = response.status;
- $scope.data = response.data;
- $window.location.href = response.data;
- }, function errorCallback(response) {
- });
- $uibModalInstance.close();
- };
- $scope.cancel = function () {
-
- $uibModalInstance.dismiss('cancel');
- };
- });
Reusable Modal using jQuery and Bootstrap
Shown in listing 14 is the HTML markup of the _ModalDialog partial view. In this view, there are placeholders for the Modal dialog title and body and several buttons.
Listing 14
- <div class="modal fade" id="SharedModalDialog" role="dialog" data-keyboard="false" data-backdrop="static">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal">×</button>
- <h4 class="modal-title">[Title]</h4>
- </div>
- <div class="modal-body">[Body] </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default"
- id="SharedModalDialogBtnOk" data-dismiss="modal">OK</button>
- <button type="button" class="btn btn-default"
- id="SharedModalDialogBtnCancel" data-dismiss="modal">Cancel</button>
- <div>
- </div>
- </div>
- </div>
Listing 15 shows the jQuery plugin namely ModalDialog to display the dialog in the _ModalDialog partial view. The plugin accepts five parameters, you can customize it to fit your requirement. The title and body parameters correspond to the dialog title and body content. The OkButtonText and CancelButtonText parameters will determine the label and the visibility of the OK and Cancel buttons respectively. If OkClickRedirect value is specified, the request will be redirected to the specified URL on OK button click.
Listing 15
- $(document).ready(function () {
- (function ($) {
-
- $.fn.ModalDialog = function (params) {
-
- params = $.extend({ title: '', body: '', OkButtonText: '', OkClickRedirect: '', CancelButtonText: '' }, params);
- if (params.title != '') {
- $(".modal-title").text(params.title);
- }
- if (params.title != '') {
- $(".modal-body").html(params.body);
- }
- if (params.OkButtonText != '') {
- $("#SharedModalDialogBtnOk").text(params.OkButtonText);
- $("#SharedModalDialogBtnOk").show();
- }
- else {
- $("#SharedModalDialogBtnOk").hide();
- }
- if (params.CancelButtonText != '') {
- $("#SharedModalDialogBtnCancel").text(params.CancelButtonText);
- $("#SharedModalDialogBtnCancel").show();
- }
- else {
- $("#SharedModalDialogBtnCancel").hide();
- }
- if (params.OkClickRedirect != '') {
- $("#SharedModalDialogBtnOk").click(function () {
- window.location.replace(params.OkClickRedirect);
- });
- }
- $('#SharedModalDialog').modal('show');
-
- return false;
- };
- })(jQuery);
- });
Listing 16 shows how to utilize the newly created partial view and jQuery plugin to display a modal dialog. The RenderPartial method can be place into the Layout page. This will be very useful if the application needs to display a simple alert message / notification to the client throughout the application. There are three buttons and examples on how to use the plugins in listing 16.
Listing 16
- <input type="button" id="btn1" value="Test Bootstrap Modal - OK button/Redirect" />
- <input type="button" id="btn2" value="Test Bootstrap Modal - multiple button" />
- <input type="button" id="btn3" value="Test Bootstrap Modal - no button" />
- @section scripts {
- @{
- Html.RenderPartial("~/Views/Shared/_ModalDialog.cshtml");
- }
- <script type="text/javascript">
- $("#btn1").click(function () {
- $('#SharedModalDialog').ModalDialog({
- title: 'Test - modal',
- body: '<b>Do you agree with the term?</b>',
- OkButtonText: 'Accept',
- OkClickRedirect: '/Home/Index/?test=1'
- });
- });
- $("#btn3").click(function () {
- $('#SharedModalDialog').ModalDialog({
- title: 'Test - modal 2 ',
- body: 'This is ANOTHER test <b>content 2222 </b> more <br/> contents .....'
- });
- });
- $("#btn2").click(function () {
- $('#SharedModalDialog').ModalDialog({
- title: 'Test Bootstrap Dialog - multiple button',
- body: 'Are you sure you want to delete this record?',
- CancelButtonText: 'Cancel',
- OkButtonText: 'Yes',
- OkClickRedirect: '/Home/Index/?test=2'
- });
- });
- </script>
- }
Listing 17 shows how to display the dialog on page load or without a button click event.
Listing 17
- $(document).ready(function () {
- $('#SharedModalDialog').ModalDialog({
- title: 'Test - Page Load',
- body: 'Display on page load !!!!'
- });
- });
Reusable Modal using AngularJS and AngularUI Bootstrap
Shown in listing 18 is the modal dialog template in the _ModalDialogNg.html file. The button visibility is based on the expression in the ngShow attribute, namely showX, showOK and showCancel respectively. The title and buttons label are accessible through the properties in the scope class. AngularJS ng-bind-html directive is being utilized to display the HTML contents securely in the div element. But still, make sure to sanitize the user-input before displaying it.
Listing 18
- <div class="modal-header">
- <button type="button" class="close" ng-show="showX" ng-click="cancel()">×</button>
- <h3 class="modal-title">{{title}}</h3>
- </div>
- <div class="modal-body">
- <div data-ng-bind-html="htmlBind"></div>
- </div>
- <div class="modal-footer">
- <button class="btn btn-default" ng-show="showOK" type="button" ng-click="ok()">{{btnOkText}}</button>
- <button class="btn btn-default" ng-show="showCancel" type="button" ng-click="cancel()">{{btnCancelText}}</button>
- </div>
Listing 19 shows the contents of the ModalPartialController.js file. AngularJS $sce (Strict Contextual Escaping) service is being use in the controller to build the trusted version of the HTML values. $uibModal is a service to create modal windows. The purpose of the $window service is to redirect the request to a particular URL on OK button click. The logic to hide or show a button and setting element labels and values are embedded in listing 19.
Listing 19
- var app = angular.module('TestAngularApp', ['ui.bootstrap']);
- app.controller('modalcontroller', function ($scope, $uibModal, $window) {
- $scope.openModal = function (title, body, okBtnText, okClickRedirect, cancelBtnText) {
- $scope.items = [title, body, okBtnText, okClickRedirect, cancelBtnText];
- var ModalInstance = $uibModal.open({
- animation: true,
- templateUrl: '/TemplatesNg/_ModalDialogNg.html',
- controller: 'InstanceController',
- backdrop: false,
- keyboard: true,
- resolve: {
- items: function () {
- return $scope.items;
- }
- }
- });
- ModalInstance.result.then(function (btn) {
- if (btn == "OK") {
- $window.location.href = okClickRedirect;
- }
- }, function () { });
- };
- });
- app.controller('InstanceController', function ($scope, $uibModalInstance, $sce, items) {
- $scope.items = items;
- $scope.title = $scope.items[0];
- $scope.body = $scope.items[1];
- $scope.btnOkText = $scope.items[2];
- $scope.btnCancelText = $scope.items[4];
- var returnUrl = $scope.items[3];
-
- $scope.htmlBind = $sce.trustAsHtml($scope.items[1]);
-
- $scope.showX = false;
- $scope.showOK = true;
- if ($scope.btnOkText == '') {
-
- $scope.showOK = false;
- }
-
- $scope.showCancel = true;
- if ($scope.btnCancelText == '') {
- $scope.showCancel = false;
- }
-
- $scope.ok = function () {
- if (returnUrl == '') {
- $uibModalInstance.close('');
- } else {
- $uibModalInstance.close('OK');
- }
- };
- $scope.cancel = function () {
-
- $uibModalInstance.dismiss();
- };
- });
Listing 20 shows how the application/view call the AngularUI Bootstrap Modal by passing in different parameters. First, include ModalPartialController.js, the AngularJS controllers from listing 19 on to the page. Then create a div element and include the ng-controller directive with controller class, modalcontroller, as its value. Add a button into the div element and on the ng-click event, specify the openModal method defined in the controller (ModalPartialController.js).
Listing 20
- <div ng-controller="modalcontroller">
- <button class="btn btn-default" type="button"
- ng-click="openModal('AngularUI Model','Click on <b>Ok</b> to Redirect to Contact page.', 'OK', '/home/Contact', 'Close')">
- Modal with redirect</button>
- </div>
- <div ng-controller="modalcontroller">
- <button class="btn btn-default" type="button"
- ng-click="openModal('@ViewBag.Something','This is the content<b>body 2</b>', '','', 'Close It')">
- Modal with close button only</button>
- </div>
- <div ng-controller="modalcontroller">
- <button class="btn btn-default" type="button"
- ng-click="openModal('AngularUI Model 2','This is a notification!', '','', 'Ok')">
- Modal with OK button only
- </button>
- </div>
- @section scripts{
- <script src="~/ControllersNg/ModalPartialController.js"></script>
- }
Listing 21 shows how to display the dialog on page load or without a button click event.
Listing 21
- <div ng-controller="modalcontroller"
- ng-init="openModal('AngularUI Model Onload','Display on page load!', '','', 'Ok')">
- </div>
Future Plan
Upgrade the code to user Angular 2 and above and MVC 6 core.
Conclusion
I hope someone will find this information useful and make your programming job easier. If you find any bugs or disagree with the contents or want help to improve this article, please drop me a line and I'll work with you to correct it. I would suggest downloading the demo and explore it in order to grasp the full concept because I might miss some important information in this article. Please send me an email if you want to help improve this article. Please use the following link to report any issues https://github.com/bryiantan/WarningBanner/issues.
Tested on: Internet Explorer 11,10,9.0, Microsoft Edge 38, Firefox 52, Google Chrome 56.0
History
4/5/2017 – Initial version
4/25/2017 - Updated source code to include additional example to display the Modal contents through AJAX POST and GET method. The examples are in the TestModalDialog page.
4/27/2017 - Updated .NET tag to ASP.NET, added reference to Resources section. Please note that the following section are not in the article, please download the source code and explore it. Please ask if you have questions.
- Enabling Cross-Origin Requests (CORS) in Global.asax file
- Enabling Cross-Origin Requests (CORS) for more than one URL
- Include Anti-Forgery token in the Ajax POST method during the Web API call
Watch this script in action
http://mvc.ysatech.com/
Download
Download Source Code (https://github.com/bryiantan/WarningBanner )
Resources
A Beginner's Tutorial for Understanding Filters and Attributes in ASP.NET MVC
Calling Webapi 2 service from MVC 5 controller
How to implement Modal popup using Angular UI in ASP.NET MVC
ng-bind-html
Passing Anti-Forgery Token to MVC Actions using Ajax Post
Why ASP.NET MVC Request.IsAjaxRequest method returns false for $http calls from angular