Combine ViewComponents in ASP.NET 5 MVC 6 - c#

I'm trying to combine multiple different ViewComponents from a controller. The ActionResult of all combined viewcomponents will be rendered to the browser.
This is based on an article which does this with PartialViews and updates the PartialViews with ajax. That article is based on previous version of MVC. For more info see: https://www.simple-talk.com/dotnet/asp.net/revisiting-partial-view-rendering-in-asp.net-mvc/
After many hours I came to the following code example. But the problem is that it works only for the first viewComponent. When I change the order of viewcomponents it still renders the first one. So it doesn't seem to have anything with my viewcomponents. Always at second loop it ends at "vc.ExecuteResultAsync(context);" with no errors. So rendering the first one is always successful.
By the way I'm using VS 2015 Enterprise with Beta7 of MVC6 and all other dependencies.
Please help!
public async Task<IActionResult> Dashboard()
{
// Combine multiple viewcomponents
return new MultipleViewResult(
ViewComponent(typeof(OrdersViewComponent))
, ViewComponent(typeof(AccountsViewComponent))
);
}
public class MultipleViewResult : ActionResult
{
public const string ChunkSeparator = "---|||---";
public IList<ViewComponentResult> ViewComponentResults { get; private set; }
public MultipleViewResult(params ViewComponentResult[] views)
{
if (ViewComponentResults == null)
{
ViewComponentResults = new List<ViewComponentResult>();
}
foreach (var v in views)
ViewComponentResults.Add(v);
}
public override async Task ExecuteResultAsync(ActionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
byte[] chunkSeparatorBytes = System.Text.Encoding.UTF8.GetBytes(ChunkSeparator);
var total = ViewComponentResults.Count;
for (var index = 0; index < total; index++)
{
var vc = ViewComponentResults[index];
// No matter which viewcomponent, this line works only with the first viewcomponent.
await vc.ExecuteResultAsync(context);
if (index < total - 1)
{
await context.HttpContext.Response.Body.WriteAsync(chunkSeparatorBytes, 0, chunkSeparatorBytes.Length);
}
}
}
}

This fails because the first vc.ExecuteResultAsync(context) will set the ContentType property and flush the response. Any succeeding call to vc.ExecuteResultAsync(context) will also try to set the ContentType property but will fail because the response has already been streamed back to the client.
I can't think of any better workaround other than creating your own instance of HttpContext that would allow you write to the ContentType property even if part of the response has already been sent back but this is messy.
If you believe this is a bug (I personally think it is because the ViewComponentResult should check if the response has already been sent before setting the ContentType), then you can always submit a bug report.

Related

Call an asynchronous method in a non-asynchronous asp.net page

I am facing an issue with two different endpoints in my single asp.net app. Basically, the issue is that one of the endpoints does not allow asynchronous methods in the page and the other endpoint does. If I run the app one endpoint will ask me to have an asynchronous asp.net page, but the other one crashes and vice versa.
public async Task<AirtableListRecordsResponse> RetrieveRecord()
{
string MyProductID = ProductID;
string baseId = "00000000000xxxx";
string appKey = "00000000000xxxx";
var records = new List<AirtableRecord>();
using (AirtableBase airtableBase = new AirtableBase(appKey, baseId))
{
Task<AirtableListRecordsResponse> task = airtableBase.ListRecords(tableName: "efls", filterByFormula: ProductID);
AirtableListRecordsResponse response = await task;
if (!response.Success)
{
string errorMessage = null;
if (response.AirtableApiError is AirtableApiException)
{
errorMessage = response.AirtableApiError.ErrorMessage;
}
else
{
errorMessage = "Unknown error";
}
// Report errorMessage
}
else
{
records.AddRange(response.Records.ToList());
var record = response.Records;
//offset = response.Offset;
//var record = response.Record;
foreach (var item in record)
{
foreach (var Fields in item.Fields)
{
if (Fields.Key == "pdfUrl")
{
string link = Fields.Value.ToString();
MyLink = Fields.Value.ToString();
}
}
}
// Do something with your retrieved record.
// Such as getting the attachmentList of the record if you
// know the Attachment field name
//var attachmentList = response.Record.GetAttachmentField(YOUR_ATTACHMENT_FIELD_NAME);
}
return response;
}
}
This is the asynchronous method which asks for an asynchronous page, the other contains a strong structure and it cannot be changed for any reason. Is there any way to make them work together?
I am using airtable.com api by the way.
Thanks in advance.
I solved by my own,
The solution I found is the following:
When a page works with two different endpoints and one of them obligates the page to be asynchronous the best solution is to split the procedures into two different sections and/or pages, one of them will call the asynchronous methods and retrieves the info and other works without being asynchronous.
How can I pass the information between the sites?
Using session variables, there are endpoints which only needs to display simple data as in this case, so the session variables will be called in the page #2 which is the non-asynchronous page.
It is a simple solution but effective.
Thank you very much to all for you answers.
Using Wait on Task, you can use synchronous method
Task<AirtableListRecordsResponse> task = Task.Run(() => airtableBase.ListRecords(tableName: "efls", filterByFormula: ProductID));
task.Wait();
AirtableListRecordsResponse response = task.Result;
Use it only when you cannot use async method.
This method is completely deadlock free as mentioned on msdn blog-
https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result-in-main-context/

Async is not working in large ASP.NET Application but in new application

I have an existing large ASP.NET (ASP.NET Web Forms + MVC support) Application and trying to implement async programming on project.
For that I created a completely new POC by selecting ASP.NET Application (again ASP.NET Web Forms + MVC support). My POC works great and correctly does async execution.
Now when I copy exact same code in existing large project, this behaves like synchronous. I already have latest .NET Framework updates in large application.
Here is my Controller implementation which is supposed to upload file async:
[HttpPost]
[ValidateInput(false)]
public async Task<JsonResult> VideoUpload(string Id)
{
for (int i = 0; i < Request.Files.Count; i++)
{
// :::::
await Run(fileStream);
}
return Json(new { error = false, message = "File uploaded." });
}
public async Task Run(Stream fileStream)
{
// :::::
using (fileStream)
{
// A long running method call
await fileUploadRequest.UploadAsync();
}
}
[HttpGet]
public JsonResult GetUploadingStatus()
{
// :::::
return Json(new { statusMessage = statusMessage, totalSent = totalSent, totalSize = totalSize }, JsonRequestBehavior.AllowGet);
}
In above code GetUploadingStatus() is a GET method does quick job. This methods receives random (3-4 seconds interval) Ajax calls and returns uploading status.
When I debug this implementation in large project, I noticed GetUploadingStatus() returns every results (10-15 Json response) at a time just after fileUploadRequest.UploadAsync() ends. So I feel this behaves like synchronous in large project. Then why this works great in a clean project but not in large project?
What I understand is that you are making parallel request: 1 request is uploading files at the same time you are making requests to GetUploadingStatus for upload progress.
In this scenario, the way ASP.NET Session State works, it could lead to behavior you mentioned. As mentioned in this answers Why would multiple simultaneous AJAX calls to the same ASP.NET MVC action cause the browser to block? 2nd request execute only after first is executed. And it is the same you are experiencing as you mentioned "..GetUploadingStatus() returns every results (10-15 Json response) at a time just after fileUploadRequest.UploadAsync() ends.."
Other than that as Alex mentioned, you are mistakenly mixing "async progrming" with "parallel programming" i.e. parallel requests in this case..
Then why this works great in a clean project but not in large project?
async/await only works correctly when running on .NET 4.5.
To upgrade an older ASP.NET project, you must do two steps:
Change the target framework to .NET 4.5 (or newer).
Change the httpRuntime.targetFramework setting in your web.config to "4.5" (or newer).
When you upgrade an existing ASP.NET project to the latest version of .NET, Visual Studio will only do the first step above, and you have to do the second step manually. However, if you create a new ASP.NET project on the latest version of .NET, Visual Studio will do both steps.
To resolve this issue I just added [SessionState(SessionStateBehavior.Disabled)] with the controller which you can see below:
[SessionState(SessionStateBehavior.Disabled)]
public class UploadController : Controller
{
[HttpPost]
[ValidateInput(false)]
public async Task<JsonResult> VideoUpload(string Id)
{
for (int i = 0; i < Request.Files.Count; i++)
{
// :::::
await Run(fileStream);
}
return Json(new { error = false, message = "File uploaded." });
}
public async Task Run(Stream fileStream)
{
// :::::
using (fileStream)
{
// A long running method call
await fileUploadRequest.UploadAsync();
}
}
[HttpGet]
public JsonResult GetUploadingStatus()
{
// :::::
return Json(new { statusMessage = statusMessage, totalSent = totalSent, totalSize = totalSize }, JsonRequestBehavior.AllowGet);
}
}
Using this annotation may open some security breaches so verify it yourself before you annotate your controller.
Hope you find this useful.

Unable to await method in ASP.NET (4.6) MVC5 project

I'm currently seeing a problem whereby my await method is just hanging, and causing the response to just hang, not doing anything until I kill the request. This is evident in both Chrome debug tools and Fiddler.
I have the following API action defined:
[Route("state/{stateCode}")]
[LogApiCallFilter]
public async Task<IList<MapPlaceDTO>> GetWithinState(string stateCode)
{
//
// Additional code truncated for SO
// Via debugging I know that the 'state' variable below is correct
//
IList<Place> places = await _placeManager.GetPlacesInState(state);
// Instantiate the list of places.
IList<MapPlaceDTO> mapPlaces = new List<MapPlaceDTO>();
// Iterate through the places and add to the map place list
foreach (Place place in places)
{
mapPlaces.Add(MapPlaceDTO.FromPlace(place));
}
return mapPlaces;
}
When I step through that code in debug mode for a unit test for the GetWithinState action, the IList<Place> places = await _placeManager.GetPlacesInState(state); method runs without exception, however I am not able to hover over the places variable to inspect it, nothing happens. Nor can I add it to the watch list, I get the following message:
error CS0103: The name 'places' does not exist in the current context
Interestingly however, if I run the exact same code within a "PlaceManager" unit test, outside of the Web API project, the test runs fine, and I can inspect the places variable.
[Fact(DisplayName = "Can_Get_All_Places_Within_State")]
[Trait("Category", "Place Manager")]
public async Task Can_Get_All_Places_Within_State()
{
State state = new State()
{
ShortName = "VIC",
Name = "Victora",
CountryCode = "AU"
};
IList<Place> places = await _placeManager.GetPlacesInState(state);
Assert.NotNull(places);
Assert.True(places.Count > 0);
}
This is the code that runs within the PlaceManager.GetPlacesInState method:
public async Task<IList<Place>> GetPlacesInState(State state)
{
if (state == null)
{
throw new ArgumentNullException("state", "The 'state' parameter cannot be null.");
}
// Build the cache key
string cacheKey = String.Format("places_state_{0}", state.Id);
// Get the places from the cache (if they exist)
IList<Place> places = CacheManager.GetItem<IList<Place>>(cacheKey);
// Get the places from the database.
if (places == null)
{
// Get the places from the database
places = await _repository.Find(i => i.State.ToLower() == state.ShortName.ToLower() && i.Country.ToLower() == state.CountryCode.ToLower());
// If there are places, then add to the cache for next time
if (places != null && places.Count > 0)
{
CacheManager.AddItem(cacheKey, places);
}
}
// return the places
return (places != null ? places : new List<Place>());
}
Does anyone have any idea why this may be occurring within the API method, but is working fine in unit tests?
As mentioned by Alexei Levenkov above, I was causing a deadlock using the getDataAsync().Result code.
While mapping my Place object to a MapPlaceDTO object, the Place object has a get property to load the place type, which would call an async function in the following way:
public PlaceType PlaceType
{
get
{
return getPlaceType().Result;
}
}
Once I removed that property and just called the GetPlaceType method directly using the await keyword, everything started working correctly. Thanks Alexei!

Differentiate Create from Update in Content Part Driver in Orchard CMS

I want to serve different views which use different ViewModel objects depending on Actions. This can be achieved in a conventional ASP.NET MVC paradigm.
[HttpGet]
public ActionResult Create() {
return View(new CreateViewModel()); //this serves Create.cshtml View
}
[HttpGet, ActionName("Create")]
public ActionResult CreatePOST(CreateViewModel viewModel) {
if (!ModelState.IsValid) {
return View(viewModel); //if error, re-serve Create.cshtml View
}
// Create new model into database
return RedirectToAction("Index");
}
[HttpGet]
public ActionResult Edit(int Id) {
var model = RetriveModel(Id);
var viewModel = new EditViewModel { Id = model.Id, Name = model.Name };
return View(viewModel); //this serves Edit.cshtml
}
[HttpPost, ActionName("Edit")]
public ActionResult EditPOST(EditViewModel viewModel) {
if (!ModelState.IsValid) {
return View(viewModel); //if error, re-serve Edit.cshtml View
}
// Update model in database
return RedirectToAction("Index");
}
How do I do the same to Orchard Content Part? It seems that the overridable Editor method in a ContentPartDriver fused both Create and Update actions together. How do I tell if the method is creating or updating? Something like
// GET
protected override DriverResult Editor(CustomContentPart part, dynamic shapeHelper) {
if (IsNewRecord) {
return ContentShape("Parts_CustomContentPart_Create" () =>
shapeHelper.EditorTemplate(TemplateName: "Parts/CreateTemplate", Model: new CreateViewModel(), Prefix: Prefix)
);
} else {
return ContentShape("Parts_CustomContentPart_Edit" () =>
shapeHelper.EditorTemplate(TemplateName: "Parts/EditTemplate", Model: BuildEditViewModel(part), Prefix: Prefix)
);
}
}
// POST
protected override DriverResult Editor(CustomContentPart part, IUpdateModel updater, dynamic shapeHelper) {
object viewModel;
if (IsNewRecord) {
viewModel = new CreateViewModel();
} else {
viewModel = new EditViewModel();
}
update.TryUpdateModel(viewModel, Prefix, null, null);
return Editor(part, shapeHelper);
}
I'm a beginner in Orchard still learning the ropes on how Orchard does things. Pardon me if my questions are too trivial.
Check for a content item id, if it is null, or possibly 0, I forget, then you are in the process of creating a content item. If it does have a value then you are editing. You can also use this in your view, can be quite handy.
If you need custom functionality to be called on creation/updating then you could consider using handler methods?
So in your parts handler add something like
OnCreated<MyPart>((ctx, part) => CreateItems(part));
Where CreateItems is a method with your part as a parameter. There are a bunch of content item events you can hook into, there is a neat list in the docs: http://docs.orchardproject.net/Documentation/Understanding-content-handlers
As always, check the source code for good examples of their usage.
EDIT
Apparently checking for null id doesn't work, I checked in some of my modules were I used it and have used the following check:
Model.ContentItem.VersionRecord == null || !Model.ContentItem.VersionRecord.Published
Although this question has been asked and answered, just thought of posting my findings so I can find it later.
ContentItem.Id is indeed 0 when the content item isn't created yet. For example, when you're about to create a new Page, ContentItem.Id == 0. Now just click on the Save button without filling up the form and validation will fail since the required field Title wasn't provided and we're getting back the same view with an error. Since validation failed, technically we don't consider the content item to be created yet. However, at this point Orchard already treating it as an existing content item. Orchard even went as far as obtaining and increasing the Identity counter of the Content Item Record table (Orchard_Framework_ContentItemRecord) from the database and assigning it as an Id to the content item.
Orchard even wired up all the Version Records, making it pretty much a full-fledged content item. All these for a content item that failed validation during creation and facing possibility of being discarded altogether. The only thing Orchard hasn't done is inserting it into the database (it's only residing in memory at this point). Therefore there's really no other ways to tell if a content item is an existing one or one that was about to be created other than checking it against the database and see if the content item was really there.
var contentItemRepository = _workContext.Resolve<IRepository<ContentItemRecord>>();
var contentItemRecord = contentItemRepository.Get(Model.ContentItem.Id);
if (contentItemRecord == null) {
isNew = true;
}
or we could also use the IContentManager to do the same thing
var contentManager = Model.ContentItem.ContentManager;
var contentItem = contentManager.Get(Model.ContentItem.Id, VersionOptions.AllVersions);
if (contentItem == null) {
isNew = true;
}
Edit:
Apparently I spoke too soon. When I said above that Orchard hasn't inserted the content item into the database yet and it still resides in memory, it actually already in the database, in a yet to be committed Transaction. In the case above where validation fails, the transaction will be rolled back at the end. The correctness of the above code depends on when it was executed. If it was executed before the transaction was cancelled and rolled back, the content item is still in the database and won't yield an accurate result. If it was executed after transaction rollback (eg. in a View), then it'll behave as expected.
How Orchard handles content item creation can be seen in Orchard.Core.Contents.Controllers.AdminController.CreatePOST(string, string, Action<ContentItem>):
_contentManager.Create(contentItem, VersionOptions.Draft);
var model = _contentManager.UpdateEditor(contentItem, this);
if (!ModelState.IsValid) {
_transactionManager.Cancel();
return View(model);
}
The content item was being created first before it was being fed into IContentManager.UpdateEditor() to validate.
Update:
Filed a bug at
https://github.com/OrchardCMS/Orchard/issues/6534

What Should I be using here? Threading? Async?

I am not sure what to use in this scenario.
I have an asp.net web api method that basically does this
Finds points of interests from foursquare near user.
Uses the foursquare locations to do queries in my database to find unique data about point of interest near user.
However since I need to store some of the foursquare information to link to my unique data to that location I decided to store all the information in my database and have my database act as my caching system.
This means anything new point of interest that comes in I have to insert into my database, check if it exists and if so then skip it or if it exists check the last refresh date(foursquare policy states all data must be refreshed after 30 day) and if it out past the refresh date I have to update the data.
I want to slow the user down and have to wait for the above to happen. I want my code to do step 1 and then do what I just mentioned while at the same time doing step 2.
Once step 2 finishes I want to return the data and let the user get on their way. If my caching system is not finished then it should keep going but not bog down the user.
I won't use any of these new results in step 2 as if I am inserting it then I won't have any data on that location at this time.
Not sure if I need to make a thread or use the async/await to achieve this.
Edit
Here is what I am trying to do
public HttpResponseMessage Get()
{
// this will do a foursquare lookup to find all stores near the user
// I want to insert them into my database and link it to my unquie data.
// stores pulled from foursquare will
// a) Be new and not in my database
// b) exist in my database but have been refreshed lately
// c) have not been refreshed in timeframe of foursquare policy
// THIS SHOULD WORK IN THE BACKGROUND
storeService.PointsOfInterestNearUser(80, -130); //As you can see it is
//void. Not sure where to put the async/await stuff
// find this product. Should be happening at the same time as above line.
var product = productService.FindProduct("Noodles");
//This will get returned to the user.
// the new stores taht are being added in StoreNearUser
//won't effect this search as I will have not data on this new store
// if existing store is being refreshed it is possible old
//address might be picked up...
//I can live with that as I doubt the address will change much.
// this should happen after product
var allStores = storeService.FindStoresThatHaveItem(product);
// this should be returned as soon as above line is finished.
//If StoreNearUser is not done, it should keep going but not hold up user.
return allStores;
}
public void StoresNearUser(double latitude, double longitude)
{
// get all categories I can about in foursquare.
//First time from db otherwise cached.
List<StoreCategory> storeCategories = GetStoreCategories();
// do a request and get everything in near the user
//(provided it is also in a category I care about)
var request = CreateFoursquareStoreRequest
(latitude, longitude, storeCategories);
// do the actual call.
var response = client.Execute<VenueSearch>(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
// start going through the results, add or update or skip of entry will happen
AddUpdateStores(storeCategories, response);
}
else
{
ErrorSignal.FromCurrentContext().Raise(response.ErrorException);
}
}
Edit 2
public async Task StoresNearUser(double latitude, double longitude)
{
// get all categories I can about in foursquare. First time from db otherwise cached.
List<StoreCategory> storeCategories = GetStoreCategories();
// do a request and get everything in near the user(provided it is also in a category I care about)
var request = CreateFoursquareStoreRequest(latitude, longitude, storeCategories);
await client.ExecuteAsync<VenueSearch>
( request
, response =>
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
AddUpdateStores(storeCategories, response);
}
else
{
ErrorSignal.FromCurrentContext()
.Raise(response.ErrorException);
}
}
);
}
gives me this error
Cannot await 'RestSharp.RestRequestAsyncHandle'
I also don't get the difference between Task and void. From what I read if you just use Task it means you are sending nothing back of meaning, then why not just use void?
Edit 2
I found this post to show me how to make the wrapper for Restsharp. It is not 100% what I want but that is a separate issue.
public async Task StoresNearUser(double latitude, double longitude)
{
List<StoreCategory> storeCategories = GetStoreCategories();
var request = CreateFoursquareStoreRequest
(latitude, longitude, maxRadius, returnLimit, storeCategories);
var response = await client.GetResponseAsync(request);
if (response.StatusCode == HttpStatusCode.OK)
{
// had to use json.net right now as the wrapper does not expose restsharps deserilizer
var venue = JsonConvert
.DeserializeObject<VenueSearch>(response.Content);
AddUpdateStores(storeCategories, venue);
}
else
{
ErrorSignal.FromCurrentContext()
.Raise(response.ErrorException);
}
}
public async Task<HttpResponseMessage>Get()
{
await storeService.PointsOfInterestNearUser(80, -130);
var product = productService.FindProduct("Noodles");
var allStores = storeService.FindStoresThatHaveItem(product);
return allStores;
}
When I watch from the debugger it looks like it is still all going in order. I think product and allStores need to be since I need the product before I can find the stores but PointsOfInterestNearUser should be going at the same time as FindProduct.
Edit 3
Here is my FindProduct Method. Not sure what to make async to me it looks like everything needs to wait.
public ResponseResult<Product> FindProduct(string barcode)
{
ResponseResult<Product> responseResult = new ResponseResult<Product>();
Product product = null;
try
{
var findBarCode = context.Barcodes.Where(x => x.Code == barcode).Select(x => x.Product).FirstOrDefault();
responseResult.Response = product;
if (product == null)
{
responseResult.Status.Code = HttpStatusCode.NotFound;
}
else
{
responseResult.Status.Code = HttpStatusCode.OK;
}
}
catch (SqlException ex)
{
ErrorSignal.FromCurrentContext().Raise(ex);
responseResult.Status.Code = HttpStatusCode.InternalServerError;
responseResult.Status.Message = GenericErrors.InternalError;
}
return responseResult;
}
Edit 4
Still not sure how to do the Task.WhenAll()
public async Task<HttpResponseMessage>Get()
{
Task[] tasks = new Task[2];
tasks[0] = storeService.PointsOfInterestNearUser(80, -130);
tasks[1] = productService.FindProduct("Noodles");
await Task.WhenAll(tasks);
// not sure how to get product back out. I looked in the debugger and saw a "Result" that has it but when I do tasks[1].Result inetllisene cannot find .Result
var allStores = storeService.FindStoresThatHaveItem(product);
return allStores;
}
I would recommend using async/await for this. Updating a cache is one of the rare situations where returning early from an ASP.NET request is acceptable. You can see my blog post on the subject for some helpful code.
So, something like this (simplified to just look up one "interesting place" per location):
public async Task<PlaceWithData> FindPlaceAsync(Location myLocation)
{
Place place = await GetPlaceFromFoursquareAsync(myLocation);
PlaceWithData ret = await GetExtraDataFromDatabaseAsync(place);
if (ret.NeedsRefresh)
BackgroundTaskManager.Run(() => UpdateDatabaseAsync(place, ret));
return ret;
}
You may also want to consider extending the ASP.NET caching system rather than doing a "roll your own" cache.

Categories

Resources