Performing an async operation before every action in a controller - asp.net-mvc

Consider the following:
public class FooController : Controller
{
public async Task<ActionResult> Foo()
{
await DoSomethingAsync();
...
return View();
}
public async Task<ActionResult> Bar()
{
await DoSomethingAsync();
...
return View();
}
...
}
This line, await DoSomethingAsync(); is repeated for each action in the controller. It'd be much nicer to do something like:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
await DoSomethingAsync();
}
But, obviously, you can't run an async op in a synchronous method. I could simply force the async op to run sync, but then you end up with the thread being blocked unnecessarily. Short of running this sync, what options do I have?

Unfortunately, ASP.NET MVC does not have asynchronous filters today. ASP.NET WebAPI does have them, and ASP.NET vNext (combining MVC and WebAPI) does have them.
So, for today I'd say you're best off repeating the code, but in the future (near future, hopefully) that could be cleaned up.

Related

Asp.Net MVC 4 - Load two pages in same time

I have a little problem with this part of code.
I want to load two pages without lock between them.
First page has a wait task (25sec).
Second has no wait tasks.
When I run my web app and call this two pages, I have to wait the end of first page to get the second page.
My question, how to load these two pages fully asynchronously?
public class AsyncTestController : Controller
{
// GET: AsyncTest
public async Task<ActionResult> Delay()
{
await Task.Run(() => Thread.Sleep(25000));
return View();
}
// GET: AsyncTest
public async Task<ActionResult> Index()
{
return View();
}
}

asp.net core validation after filters

I want to run some custom logic for all APIs (asp.net core) that we have in our service before model validation but after model binding. Is this possible? I tried an ActionFilter but it gets called after validation. Resource filter also does not work for us. Appreciate your help.
Web API controllers don't have to check ModelState.IsValid if they have the [ApiController] attribute. In that case, an automatic HTTP 400 response containing issue details is returned when model state is invalid.
One way to achieve what you want is to suppress this behavior.
Add the following code to ConfigureServices:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Then you can add your code to the filter - eg:
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
if(context.ActionArguments != null && context.ActionArguments.Count > 0)
{
//WARNING - you should add "safe" code to access the dictionary
//I have hardcoded the parameter name (data) here for sample only.
var model = context.ActionArguments["data"];
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
}
}
of course you need to apply the filter as well - in the example case below, I have applied it globally. You can be more specific if you want.
services.AddMvc(
options => options.Filters.Add(new SampleActionFilter())
).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
In your controller code, you can also further use the TryValidateModel method if you want, like so:
[Route("api/[controller]")]
[ApiController]
public class ProcessController : ControllerBase
{
[HttpPost]
public IActionResult Contact(FormDataModel data)
{
bool validated = TryValidateModel(data);
if (!ModelState.IsValid)
{
ModelState.AddModelError("", "Id cannot be empty..");
return Ok();
}
return Ok();
}
}
Hope this helps to solve your problem.

Async HttpPost requests - ASP.NET MVC 5

I'm trying to make an async HttpPost requests with Ajax & MVC.
I know that SessionId is the problem: multiple requests from the same client with the same SessionId will add the second request to the queue.
after I google it I saw some answers for this it fails to me.
I tried to clear session data.
I tried to add an mvc attribute to change session mode to readOnly:
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class TestController : SurfaceController
{
[HttpPost]
public ActionResult Test1()
{
Thread.Sleep(5000);
return Json("1");
}
[HttpPost]
public ActionResult Test2()
{
Thread.Sleep(5000);
return Json("2");
}
}

MVC 5 redirect from BeginExecuteCore to another controller

I try to redirect from function BeginExecuteCore to another controller
all My controller inheritance the function BeginExecuteCore and I want do some logic if something happen so redirect to "XController"
How to do it?
EDIT:
Balde:
I use function BeginExecuteCore I can't use Controller.RedirectToAction
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
//logic if true Redirect to Home else .......
return base.BeginExecuteCore(callback, state);
}
The Balde's solution is working but is not optimal.
Let's take an example :
public class HomeController : Controller
{
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
Response.Redirect("http://www.google.com");
return base.BeginExecuteCore(callback, state);
}
// GET: Test
public ActionResult Index()
{
// Put a breakpoint under this line
return View();
}
}
If you run this project you will obviously get the Google main page. But if you look at your IDE you'll note that the code is waiting for you due to the breakpoint.
Why ? Because you redirected the response but didn't stop the flow of ASP.NET MVC and so, it continues the process (by calling the action).
It's not a big issue for a small website but if you forecast to have a lot of visitors, this can become a serious performance issue : potentially thousand of requests per second that run for nothing because the response is already gone.
How can you avoid that ? I have a solution (not a pretty one but it does the job) :
public class HomeController : Controller
{
public ActionResult BeginExecuteCoreActionResult { get; set; }
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
this.BeginExecuteCoreActionResult = this.Redirect("http://www.google.com");
// or : this.BeginExecuteCoreActionResult = new RedirectResult("http://www.google.com");
return base.BeginExecuteCore(callback, state);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = this.BeginExecuteCoreActionResult;
base.OnActionExecuting(filterContext);
}
// GET: Test
public ActionResult Index()
{
// Put a breakpoint under this line
return View();
}
}
You store your redirect results inside a controller member and you execute it when the OnActionExecuting is running !
Redirect from Response:
Response.Redirect(Url.RouteUrl(new{ controller="controller", action="action"}));
Im try write this and success:
Response.RedirectToRoute(new { controller = "Account", action = "Login", Params= true });

asp .net mvc authorization

What is the best way to protect certain areas of your web application in asp .net mvc. I know we can put [Authorization] attribute at each action, but this seems very tedious since you have to put it all over the place. I'm using membership provider and trying the way I used to do in postback model by setting this protection based on the folder. I use web.config <location> section to protect some folders. I tried this in mvc, it seems to be working, but most of tutorial uses the [Authorization] way.
Which one is the better method?
I'd highly recommend against putting it in the web.config. Actually, so do Conery, Hanselman, Haack, and Guthrie -- though not highly (p223 of Professional ASP.NET MVC 1.0)
Routes are subject to change, especially in MVC. With the WebForm model, routes are physically represented on the file system so you didn't really have to worry about it. In MVC, routes are "dynamic" for lack of a better term.
You could end up with multiple routes mapping to one controller causing a maintenance pain in the web.config. Worse, you could inadvertently have a route invoke a controller accidentally or forget to update the web.config after adding/modifying routes and leave yourself open.
If, however, you secure your controller instead of the actual route, then you don't need to worry about keeping the web.config in sync with the goings-on of the controllers and changing routes.
Just my 2 cents.
One possible solution is to create a "protected controller" and use it as a base class for all the areas of your application that you want to protect
[Authorize]
public class ProtectedBaseController : Controller {
}
public class AdminController : ProtectedBaseController {
...
}
public class Admin2Controller : ProtectedBaseController {
...
}
put [Authorisation] at the top of the controller class. that will lock down the entire controllers actions.
You can put [Authorize] to every contoller you need to secure.
You can add filter GlobalFilters.Add(new AuthorizeAttribute()); in your Startup.cs (or Global.asax) and put [AllowAnonymus] attribute to any controller or action you allow to non-registered users.
If you chose to put [Authorize] to every secure contoller you need to be sure that any controller added by you or anyone other in team will be secure. For this requirement I use such test:
[Fact]
public void AllAuth()
{
var asm = Assembly.GetAssembly(typeof (HomeController));
foreach (var type in asm.GetTypes())
{
if (typeof(Controller).IsAssignableFrom(type))
{
var attrs = type.GetCustomAttributes(typeof (AuthorizeAttribute));
Assert.True(attrs.Any());
}
}
}
I think this way is better than a creating ProtectedContoller, because it make no guarantee that you system have all controllers secure. Also this way doesn't use inheritance, which make project heavier.
Authorization is one way to secure your application; is to apply the attribute to each controller.
Another way is to use the new AllowAnonymous attribute on the login and register actions.
Making secure decisions based on the current area is a Very Bad Thing and will open your application to vulnerabilities.
Code you can get here
As ASP.NET MVC 4 includes the new AllowAnonymous attribute, so you no more need to write that code.
After setting the AuthorizeAttribute globally in global.asax and then whitelisting will be sufficient.
This methods you want to opt out of authorization is considered a best practice in securing your action methods. Thanks.
[Area("AdminPanel")]
public class TestimonialsController : Controller
{
private AppDbContext _context;
private IWebHostEnvironment _env;
public TestimonialsController(AppDbContext context, IWebHostEnvironment env)
{
_context = context;
_env = env;
}
public IActionResult Index()
{
return View(_context.Testimonials);
}
// GET: AdminPanel/Testimonials/Create
public IActionResult Create()
{
return View();
}
// POST: AdminPanel/Testimonials/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Testimonial testimonial)
{
if (!ModelState.IsValid)
{
return View();
}
if (!testimonial.Photo.CheckFileType("image/"))
{
return View();
}
if (!testimonial.Photo.CheckFileSize(200))
{
return View();
}
testimonial.Image = await testimonial.Photo.SaveFileAsync(_env.WebRootPath, "images");
await _context.Testimonials.AddAsync(testimonial);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
// GET: AdminPanel/Testimonials/Edit/5
public async Task<IActionResult> Update(int? id)
{
if (id == null)
{
return BadRequest();
}
var testimonial = await _context.Testimonials.FindAsync(id);
if (testimonial == null)
{
return NotFound();
}
return View(testimonial);
}
// POST: AdminPanel/Testimonials/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(int? id, Testimonial newtestimonial)
{
if (id==null)
{
return BadRequest();
}
var oldtestimonial = _context.Testimonials.Find(id);
if (oldtestimonial == null)
{
return NotFound();
}
if (!ModelState.IsValid)
{
return View();
}
if (!newtestimonial.Photo.CheckFileType("image/"))
{
return View();
}
if (!newtestimonial.Photo.CheckFileSize(200))
{
return View();
}
var path = Helper.GetPath(_env.WebRootPath, "images", oldtestimonial.Image);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
newtestimonial.Image = await newtestimonial.Photo.SaveFileAsync(_env.WebRootPath, "images");
oldtestimonial.Image = newtestimonial.Image;
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
public async Task<IActionResult> Delete(int id)
{
if (id == null)
{
return BadRequest();
}
var testimonial = _context.Testimonials.Find(id);
if (testimonial == null)
{
return NotFound();
}
var path = Helper.GetPath(_env.WebRootPath, "images", testimonial.Image);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
_context.Testimonials.Remove(testimonial);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
}

Resources