MVC Ajax request without blocking main thread - asp.net

i have some action inside controller likes:
public class ValuesController : Controller
{
[HttpPost]
public string GetInfo()
{
Thread.Sleep(30000); // logics imitation
return "result";
}
}
when I send request from client-side on this action I'll receive "Main Thread blocking" (like deadlock) while awaiting "logics imitations"
how i can prevent it?
already tried:
public class ValuesController : Controller
{
[HttpPost]
public async Task<string> GetInfo()
{
return await Task.Factory.StartNew(() =>
{
Thread.Sleep(30000);
return "result";
});;
}
}
Not working...
already looked (ASP.NET MVC and Ajax, concurrent requests?), but SessionState.ReadOnly way is not for me...
also tried using .svc service instead controller-action but have same troubles.

MVC by default blocks parallel sessions, and there is only one way to avoid it:
[SessionState(SessionStateBehavior.ReadOnly)]
Attribute on Controller and clearly separating logics with using writing and reading sessions.

This question was posted by my colleague. Yes, we have the situation where we need to "fire and forget" (we are trying to call actions asynchronically, when different actions are executed at the same time, but still all we have managed to get is to call actions one after another)

Related

ASP.NET Core 2.1 CreatedAtRoute Returns no Response

I was searching around but I couldn't find a working answer for my issue. I saw a similar question but we had different results. My issue is I have 2 controllers. The first controller has a POST action that I want to return a CreatedAtRoute to a GET action inside a different controller. I tested the GET action in Postman and it works so my only issue is getting the CreatedAtRoute to work.
My first controller:
[HttpPost]
public async Task<IActionResult> Submit(AssessmentAttemptVM attempt)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
//Do database related stuff
await _context.SaveChangesAsync();
return CreatedAtRoute("GetAssessmentResult", new { id = studentAttempt.Id }, studentAttempt);
}
My second controller:
[HttpGet("{id}", Name = "GetAssessmentResult")]
public async Task<ActionResult<AssessmentResultVM>> GetById(int id)
{
//Get my ViewModel -- This works if I try to access it without using the CreatedAtRoute method
return resultVM;
}
The picture below shows what Postman responds with when I try to Post. I verified that the data gets added to the database so only the CreatedAtRoute method is the only I can think of that isn't making this work for me..
EDIT
Controller Route Attributes:
[ApiController]
[Route("api/view/assessmentresult/")]
public class AssessmentResultsController: ControllerBase
{
[ApiController]
[Route("api/take/assessment")]
public class StudentAssessmentController : ControllerBase
{
I found the cause. The last parameter for CreatedAtRoute and CreatedAtAction required an object similar to the destination controller. It went over my head because I was sending models prior to what I did now which used a ViewModel.
Well That wasn't the main reason I couldn't get a response though. It was because of an execption where the object I'm passing ended up having recursive references because I was sending a model that wasn't properly formatted to be sent over as JSON.
I used this to make it work, taken from the MS Docs site:
CreatedAtAction(String, String, Object, Object) Where the last parameter should be the object you want to the api to send over.
PS: I also didn't notice immediately because when I debugged the project, it didn't crash and had to read the logs. I'm a noob so I really didn't know that it's possible for an exception to occur without the project crashing in debug mode.

Not able to redirect to action when using TempData in Asp.Net Core

I am trying to achieve a simple thing in Asp.Net Core. This would have been no big deal in Asp.Net Mvc. I have an action method like this
public async Task<IActionResult> Create([Bind("Id,FirstName,LastName,Email,PhoneNo")] Customer customer)
{
if (ModelState.IsValid)
{
_context.Add(customer);
await _context.SaveChangesAsync();
TempData["CustomerDetails"] = customer;
return RedirectToAction("Registered");
}
return View(customer);
}
public IActionResult Registered()
{
Customer customer = (Customer)TempData["CustomerDetails"];
return View(customer);
}
At first I assumed TempData works by default but later realized that it has to be added and configured. I added ITempDataProvider in startup. The official document seems to describe that this should be enough. It didn't work. Then I also configured it to use Session
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddSession(
options => options.IdleTimeout= TimeSpan.FromMinutes(30)
);
services.AddMvc();
services.AddSingleton<ITempDataProvider,CookieTempDataProvider>();
}
I the following line related to Session in Configure method of Startup before writing app.UseMvc.
app.UseSession();
This still is not working. What is happening is I am not getting any exception because of use of TempData anymore which I got before when I missed some of the configuration but now the create action method is not able to redirect to Registered Method. Create method does all the work but RedirectToAction has no effect. If I remove the line that is assigning Customer details to TempData, the RedirectToAction successfully redirects to that action method. However in this case Registered action method don't have access to CustomerDetails obviously. What am I missing?
#Win. You were right. I realized the serialization, deserialization is required whenever you want to use TempData in Asp.net Core after reading the disclaimer in this article.
https://andrewlock.net/post-redirect-get-using-tempdata-in-asp-net-core/
I first tried to use BinaryFormatter but discovered that it has also been removed from .NET Core. Then I used NewtonSoft.Json to serialize and deserialize.
TempData["CustomerDetails"] = JsonConvert.SerializeObject(customer);
public IActionResult Registered()
{
Customer customer = JsonConvert.DeserializeObject<Customer>(TempData["CustomerDetails"].ToString());
return View(customer);
}
That's the extra work we have to do now but looks like that's how it is now.

what is the best way to consume web api in mvc

I am using web api in my mvc application. I have method in web api which returns user detail using userId (which is in session["userID"])
public object getUserDetail()
{
//here is need of session["userID"]
// return somthing
}
so what is best way to access this web api method from jquery . Should i access this directly or first i should call my controller method and from there i should call this web api method.
You can directly call WebApi from jquery for performing operations(like insert/update/delete)other than returning JSON for processing back. For the scenarios where you require manipulating your view, call mvc controller which calls the Webapi.
So, for your case, the getUserDetail() method returns data. If these return values needs to be used in your view, then call it from mvc controller
WebApi is already an exposed endpoint for you to access your data from. Going to your controller, and calling the method from there diminishes the intent of having exposed the method as an Api in the first place. Try making a call to the route of the Api method, and you should be fine.
On a side note, try exposing a strongly typed object instead of just returning an object.
what is best way to access this web api method from jquery
Simply make an ajax call.
var url = www.example.com/api/user;
$.ajax({
type: 'GET',
url: url,
success: function(userValue) {
// Do something with your user info...
},
error: function(error) {
// Something went wrong. Handle error.
}
});
And have your controller return the value.
public class UserController : ApiController
{
[HttpGet] // For clarity only
public object Get()
{
// return your object.
return session["userID"];
}
}
And to get your url for the controller, you can use this in your view.
Url.HttpRouteUrl("DefaultApi", new {controller = "UserController "})})
Where DefaultApi is the route name defined in your route table (usually in RouteConfig.cs).
Edit:
Regarding access to session there's a number of ways to get around it. Take a look at this question and I think you will solve it. Accessing Session Using ASP.NET Web API
Or this tutorial:
http://www.codeproject.com/Tips/513522/Providing-session-state-in-ASP-NET-WebAPI
public class SessionableControllerHandler : HttpControllerHandler, IRequiresSessionState
{
public SessionableControllerHandler(RouteData routeData)
: base(routeData)
{}
}
public class SessionStateRouteHandler : IRouteHandler
{
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
return new SessionableControllerHandler(requestContext.RouteData);
}
}
And lastly register it with your route:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
).RouteHandler = new SessionStateRouteHandler();
Or add this to your Global.asax.cs
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
Feels like you might want to step back and rethink the basics. The main question here is: does it sound right that one view layer (MVC) calls another view layer (web api)? And simple answer is: no.
Usual setup is that your ajax calls target your Web Api controller methods directly. But if for whatever reason you find yourself thinking that you really need your MVC to call WebApi then that looks for extracting business logic to separate layer/tier so what you end up with is both, MVC and Web API, calling same method in separate class/layer (whatever your methods actually do).
So, instead of:
//this is in your MVC controller
public ActionResult SomeMVCAction(){
MyWebApiMethod();
}
//This is in your web api controller
public SomeStrongType MyWebApiMethod(){
var sum = 2+2;
}
you might want to have something like:
//this is in your MVC controller
public ActionResult SomeMVCAction(){
DoSum();
}
//This is in your web api controller
public SomeStrongType MyWebApiMethod(){
DoSum()
}
///This function is defined in separate layer/project which is your business layer
public static int DoSum(){
return 2+2;
}
PS.
Regarding session...There is a reason why session is not (easily) accessible in WebApi. REST Api should be stateless so you might want to rethink your design where you need session in web api controller.
You can describe a problem you're trying to solve by accessing session in web api controller and then we can try to give opinion on that.

Ajax with ViewComponent

How to use AJAX for ViewComponent in Asp.Net Core? That is calling the method #Component.Invoke ("ComponentName", SomeData); ViewComponent will involve various views depending on SomeData without rebooting the main view.
Update
My solution is:
$(function () {
$(Container).load('ControllerName/ControllerAction', {
ArgumentName: ArgumentValue });
});
Controller :
public IActionResult ControllerAction(string value)
{
return ViewComponent("ViewComponent", value);
}
Is there a way to directly use a ViewComponent as AjaxHelpers in previous versions?
Your solution is correct. From the docs:
[ViewComponents are] not reachable directly as an HTTP endpoint, they're invoked from your code (usually in a view). A view component never handles a request.
While view components don't define endpoints like controllers, you can easily implement a controller action that returns the content of a ViewComponentResult.
So as you suggested, a lightweight controller can act as an AJAX proxy to our ViewComponent.
public class MyViewComponentController : Controller
{
[HttpGet]
public IActionResult Get(int id)
{
return ViewComponent("MyViewComponent", new { id });
}
}

WebAPI service design

I'm pretty comfortable with how Asp.NET MVC controllers worked when designing services.
However the new WebAPI controllers. how am I supposed to design my services here?
Lets say we have 3 different ways to list e.g. Users.
Get 10 latest , Get all, Get inactive or whatever.
none of these might need parameters. so how would you solve this in WebAPI
IEnumerable<User> Get10Latest()
IEnumerable<User> GetAll()
IEnumerable<User> GetInactive()
That won't work since they have the same param signature.
So what is the correct way to design this here?
You can support multiple methods in one controller for a single HTTP method by using the action parameter.
E.g.
public class UsersController : ApiController
{
[ActionName("All")]
public HttpResponseMessage GetAll()
{
return new HttpResponseMessage();
}
[ActionName("MostIQ")]
public HttpResponseMessage GetMostIQ()
{
return new HttpResponseMessage();
}
[ActionName("TenLatest")]
public HttpResponseMessage GetTenLatest()
{
return new HttpResponseMessage();
}
}
Unfortunately, I have not found a way to get a single controller to handle both with and without the action at the same time.
e.g.
public class UsersController : ApiController
{
[ActionName("")] // Removing this attribute doesn't help
public HttpResponseMessage Get()
{
return new HttpResponseMessage();
}
[ActionName("All")]
public HttpResponseMessage GetAll()
{
return new HttpResponseMessage();
}
[ActionName("MostIQ")]
public HttpResponseMessage GetMostIQ()
{
return new HttpResponseMessage();
}
[ActionName("TenLatest")]
public HttpResponseMessage GetTenLatest()
{
return new HttpResponseMessage();
}
}
Being able to use a single controller for a collection resource and all of its subsets would be nice.
Someone will probably be along and wrap me on the knuckles for this, but you need to configure your routing to handle the Gets. This is how I got it working with the above operations:
config.Routes.MapHttpRoute(
name: "CustomApi",
routeTemplate: "api/{controller}/{action}",
defaults: new { id = RouteParameter.Optional }
);
So now your requests are mapped to the correct controller -> action via the route template. Note that the new route needs to be registered first in WebApiConfig.cs. If you keep the old, default one.
EDIT
Having re-read the question I realize I wasn't quite answering the design question. I would think that one way to go about it, from a REST perspective, would be to use a separate resource to expose the proper collections (Get10Latest for example) since I assume that there is a business reason for exposing that exact subset of data through the service. In that case you'd expose that resource though a single Get in its own Controller (if that is the desired behaviour).
Well why not have urls like this:
GET /users
GET /users/latest
GET /users/inactive
Using routing you could route them to
public classs UserController : ApiController
{
public IEnumerable<User> Get(string mode)
{
// mode is in routing restricted to be either empty, latest, or inactive
}
}
Otherwise use multiple controllers. The use of action names in Web API is kind of a wrong way to about it.

Resources