Customized validation for AuthorizeAttribute keeps the process running - asp.net

I have the following customized validator :
public class CAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//if ajax request set status code and end responcse
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.Write(/*some data*/);
filterContext.HttpContext.Response.End();
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
and the following Action
[HttpPost]
[CAuthorize]
public ActionResult Comment(Comment comment_obj)
{
if (ModelState.IsValid)
{
this.tblSomthing.comments.Add(comment_obj);
this.db.SaveChanges();
}
return /*some view*/
}
why when the validator fails the action is executed and the DB record is saved but the response is correct (the one that is set in the validator)
I just want to stop the execution of the action if the validator fails.

You know that an action needs to return a view in order the user sees some form of output? Using Response.Write or Response.End in a controller does nothing.
Anyway what you have there is an attribute, not a validator.
If it were a validator it'd be inside the model used in the view, applied to a property.

Related

About ASP.NET Web Pages Global

I am new learner to asp.net. I saw “_appstart.cshtml”, “_pagestart.cshtml” and “_viewstart.cshtml” which act like global headers or footer.
(1)If I want to trigger something right before the page is output, should I put the code in _viewstart.cshtml of others?
(2)Let C be the html code just before output, beside appending code to C can I replace code from C? Such as making all text uppercase or replace some text?
(3)Will asp.net cache this process so that I won't run each time?
benone
Answer to Point 1
The _ViewStart file can be used to define common view code that you want to execute at the start of each View’s rendering. For example, we could write code within our _ViewStart.cshtml file to programmatically set the Layout property for each View to be the SiteLayout.cshtml file by default
Actually it's like a BasePage in ASP.Net where we can keep the common code.
Or you can write the logic directly in the View like below.
#{
Layout = "~/Views/Shared/_Layout.cshtml";
if (Some Consition) {
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
}
Alternatively
You can override the Action Executing method, which executes before executing the Action Method. You can set it for a particular Action method or for the Complete Controller
Below is the code for setting it for Complete Controller.
protected override void OnActionExecuting(ActionExecutingContext ctx) {
base.OnActionExecuting(ctx);
}
Below is the Code for Setting it for Particular Action method
[MyAttribute(SomeProperty = "")]
public ActionResult Index()
{
return View("Index");
}
public class MyAttribute : ActionFilterAttribute
{
public string SomeProperty { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
}
Answer to Point 2
You can use
var str = Html.Partial("_Partial_View_Name");
Partial returns an MvcHtmlString. You can intercept the output by setting it to a variable and make the necessary change.
Answer to Point 3
Yes. Below is the sample code
namespace MvcApplication1.Controllers
{
[HandleError]
public class HomeController : Controller
{
[OutputCache(Duration=10, VaryByParam="none")]
public ActionResult Index()
{
return View();
}
}
}
The output of the Index() action is cached for 10 seconds

Unable to pass data form SecurityAttribute class to controller in MVC3

i am using mvc 3 code first. facing problem while passing data form SecurityAttribute class to Controller. i actually want to redirect user on login page with displaying Message. for this i override AuthorizeCore method in SecurityAttribute class. in this method i am unable to direct use session, cookies, tempdate, and viewbag etc. any other solution to solve this problem. Thanks
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["UserID"] == null)
{
//here i am unable to pass message to User/LogOn action.
httpContext.Response.Redirect("~/User/LogOn");
// httpContext.Session["lblMsg"] = "You are not authroize to perform this action.Please Login through different account";
return false;
}
First things first, you should not redirect inside the AuthorizeCore method. You should use the HandleUnauthorizedRequest method which is intended for this purpose. As far as passing an error message to the LogOn action is concerned you could use TempData:
public class SecurityAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// perform the custom authorization logic here and return true or false
// DO NOT redirect here
return httpContext.Session["UserID"] != null;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Controller.TempData["ErrorMessage"] = "You are not authroize to perform this action.Please Login through different account";
// calling the base method will actually throw a 401 error that the
// forms authentication module will intercept and automatically redirect
// you to the LogOn page that was defined in web.config
base.HandleUnauthorizedRequest(filterContext);
}
}
and then inside the LogOn action:
public ActionResult LogOn()
{
string errorMessage = TempData["ErrorMessage"] as string;
...
}
or if you want to access it inside the LogOn.cshtml view:
<div>#TempData["ErrorMessage"]</div>
Another possibility is to pass the message as a query string parameter instead of using TempData:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var values = new RouteValueDictionary(new
{
controller = "User",
action = "LogOn",
errormessage = "You are not authroize to perform this action.Please Login through different account"
});
filterContext.Result = new RedirectToRouteResult(values);
}
and then you could have the LogOn action take the error message as action parameter:
public ActionResult LogOn(string errorMessage)
{
...
}

A Contact page with ASP.NET MVC 3

I have a contact page and this page shall either show a form or a success message or a failure message, so basically something like this:
#model MyApp.Models.ContactData
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div>
...Some static content...
If page was opened the first time
-> Render a form here
Else If form was posted and data successfully processed
-> Render a success message here
Else If form was posted but error occurred during processing
-> Render a failure message here
...Some static content...
</div>
I don't know what's the best way to achieve this with MVC 3. Do I create three completely separate views (which is something I'd like to avoid because of the static content which would be the same for all three views)? Or could I create three partial views and then decide based on an additional flag I could put into the model class which partial view to render? Or can I inject somehow the partial views dynamically from the controller into the view?
The controller I have so far looks like this:
public class ContactController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(ContactData contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
bool result = service.Process(contactData);
return ?; // What do I return now? It must somehow depend on result.
}
else
return View(contactData));
}
}
I had a similar page and behaviour with ASP.NET WebForms and the solution was there to put the three variable blocks of markup into asp:Panel controls and then switch on or off the Visible flag of those panels from code-behind. I guess I need quite another approach with ASP.NET MVC to reach the same goal.
What is the best way?
Thank you for suggestions in advance!
You can try this way:
[HttpPost]
public ActionResult Index(Contact contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
if (service.Process(contactData))
{
TempData["Success"] = "Your success message.";
return RedirectToAction("Index");
}
else
{
TempData["Error"] = "Your fail message.";
}
}
return View(contact);
}
Perhaps use the ViewBag to help achieve all this. Of course it's a dynamic, so you can add & check for any prop you want/need/expect.
[HttpPost]
public ActionResult Index(ContactData contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
bool result = service.Process(contactData);
ViewBag.ContactSuccess = true;
}
else
{
ViewBag.ModelStateErr= "some err";
}
return View(contactData));
}
Then in your View:
if (ViewBag.ContactSuccess !=null && ((bool)ViewBag.ContactSuccess))
{
//thanks for posting!
}
else
{
if (ViewBag.ModelStateErr !=null)
{
//show that we have an err
}
else
{
//we have no err nor a 'true' contact success yet
//write out the form
}
}
Looks like that you can issue an ajax call on the client side, and based on the Json result, you can render different content from the client side.
I'd suggest coding up three different Views
index.cshtml
contactSuccess.cshtml
contactFail.cshtml
Then in your Controller, you'll have similar code as before
public class ContactController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(ContactData contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
bool result = service.Process(contactData);
return View("contactSuccess.cshtml");
}
else
return View("contactFail.cshtml", contactData);
}
}
This way each view has an independent and you don't have a big inline IF block in the middle of your markup.
Alternatively (and this is how I'd do it) you can have the index.cshtml contain three partials...
_ContactForm.cshtml
_ContactSuccess.cshtml
_ContactFail.cshtml
and then you can load the partial views into the index view, and even swap them out dynamically using AJAX.

ASP.NET MVC 2.0 Custom Client Validation

I am trying to make a validator that will make sure that at least 2 items are selected. The validator works correctly on the server side but the client side code never gets executed.
Here is the code:
Sys.Mvc.ValidatorRegistry.validators["country"] = function (rule) {
var min = rule.ValidationParameters["min"];
return function (value, context) {
if (value >= min) return true;
return rule.ErrorMessage;
};
};
And here is the validator code:
public class CountryValidator : DataAnnotationsModelValidator<CustomValidations.CountryAttribute>
{
private int _minimum;
private string _message;
public CountryValidator(ModelMetadata metadata, ControllerContext context, CustomValidations.CountryAttribute attribute) : base(metadata,context,attribute)
{
_minimum = attribute.Minimum;
_message = attribute.ErrorMessage;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = _message,
ValidationType = "country"
};
rule.ValidationParameters.Add("min", _minimum);
return new[] { rule };
}
}
I have even registered the validation adapter in global.asax file:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(AgeAttribute), typeof(AgeValidator));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CountryAttribute),typeof(CountryValidator));
}
I am thinking that the validator only works with the elements that have a value property like textboxes etc.
UPDATE 1:
EnableClientValidation is invoked correctly and all the required JS files are included in the project. It seems like I need to attach the onblur to the context. I will try that and post the results.
<% =Html.EnableClientValidation(); %> needs to be in your view somewhere. Also make sure you reference MicrosoftAjax.js and MicrosoftMvcValidation.js in the same view (before your js function).
Either your missing MicrosoftMvcAjax.js or you need to implement your custom validation in jQuery as described on Mr. Haack's website http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx.
I think it is because the default validation is invoked on the onblur event of the input textbox. And for a listbox this event was not being thrown.

ASP.NET Client Side Postback

I have your basic asp.net web form which contains some client-side JavaScript that forces the page to time out and then redirect after 5 minutes. Mainly to protect possibly sensitive information.
At timeout, I want to force a server post back allowing me to save the form values for future edits.
I have played with both ClientScript.GetPostBackClientHyperlink() and ClientScript.GetPostBackEventReference(). Both seem to cause EventValidation issues for me. Yes, I can turn off Event Validation but is there a different or better workaournd?
Ideally, I don’t want to invoke a control (which has to be displayed) but just cause a postback with some type of argument that I can recognize serverside as being the result of a timeout condition.
This may be overkill, but you could setup a javascript timer to fire a web service call. The .NET web service can accept the form data and save it.
No great solutions so I build my own. A pretty simple custom control. Comments welcome.
using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace BinaryOcean.Web.Library.WebControls
{
[ToolboxData("<{0}:PostBackTimer runat=\"server\" />"), DefaultProperty("Seconds"), DefaultEvent("Timeout")]
public class PostBackTimer : Control, IPostBackEventHandler
{
public PostBackTimer() { }
public string CommandArgument
{
get { return (string)ViewState["CommandArgument"] ?? string.Empty; }
set { ViewState["CommandArgument"] = value; }
}
public string CommandName
{
get { return (string)ViewState["CommandName"] ?? string.Empty; }
set { ViewState["CommandName"] = value; }
}
public bool Enabled
{
get { return (bool)(ViewState["Enabled"] ?? true); }
set { ViewState["Enabled"] = value; }
}
public int Seconds
{
get { return (int)(ViewState["Seconds"] ?? 0); }
set { ViewState["Seconds"] = value; }
}
[Description("PostBackTimer_OnTimeout")]
public event EventHandler Timeout = delegate { };
[Description("PostBackTimer_OnCommand")]
public event CommandEventHandler Command = delegate { };
public void RaisePostBackEvent(string eventArgument)
{
Timeout(this, EventArgs.Empty);
Command(this, new CommandEventArgs(CommandName, CommandArgument));
}
protected override void OnPreRender(EventArgs e)
{
if (Enabled && Seconds > 0)
{
var postback = Page.ClientScript.GetPostBackEventReference(this, null);
var script = string.Format("setTimeout(\"{0}\",{1});", postback, Seconds * 1000);
Page.ClientScript.RegisterStartupScript(GetType(), "PostBackTimer_" + UniqueID, script, true);
}
base.OnPreRender(e);
}
}
}
Couldn't you use a javascript timer to "click" the submit button? It sounds like using this form would be really annoying though, if it keeps posting back while you're trying to fill it out.
with javascript, call __doPostBack("clientIdOfSubmitButton", null). This will fire off a postback just as if that button (or any other control you want) had triggered it.

Resources