Using complex route Razor Page routing conventions that involve multiple variables in paths - url-rewriting

Background
.NetCore 2 web application targetting the full .Net framework. We are moving from the conventional MVC separate Controller/View structure where we configured route rules using [Route()]/Get() attributes to handle routing to Razor Pages.
So far
Following the MS docs (https://learn.microsoft.com/en-us/aspnet/core/mvc/razor-pages/razor-pages-convention-features#page-model-action-conventions) I have the first tier of folders with a category ID being passed to the page;
e.g. '/category/15032/dashboard/' resolves to the page 'pages/category/dashboard'
`options.Conventions
.AddFolderRouteModelConvention("/category", model =>
{
int selectorCount = model.Selectors.Count;
for (int i = 0; i < selectorCount; i++)
{
SelectorModel selector = model.Selectors[i];
string[] segments = selector.AttributeRouteModel.Template.Split('/');
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates($"category/{{categoryid}}/{segments[1].ToLower()}", "{categoryPageTemplate?}")
}
});
}
});`
This works as expected. However, the problems arise when I try to add further depth to the folder structure.
The problem
Each category contains a sub-category, and then within that, an item. For example, I will include a deeply nested page.
e.g. '/category/15032/subcategory/543/item/211/status' should resolve to the page 'pages/category/subcategory/item/status'
The hope was that adding the following convention would handle this;
`options.Conventions
.AddFolderRouteModelConvention("/category/subcategory/item", model =>
{
int selectorCount = model.Selectors.Count;
for (int i = 0; i < selectorCount; i++)
{
SelectorModel selector = model.Selectors[i];
string[] segments = selector.AttributeRouteModel.Template.Split('/');
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates($"category/{{categoryid}}/subcategory/{{subcategoryid}}/item/{{itemid}}/{segments[2].ToLower()}",
"{itemPageTemplate?}")
}
});
}
});`
Error
Microsoft.AspNetCore.Routing.RouteCreationException: The following
errors occurred with attribute routing information:
For action: 'Page: /category/subcategory/item/dashboard' Error: The
route parameter name 'categoryid' appears more than one time in the
route template. Parameter name: routeTemplate
at
Microsoft.AspNetCore.Mvc.Internal.AttributeRoute.GetRouteInfos(IReadOnlyList
1 actions) at
Microsoft.AspNetCore.Mvc.Internal.AttributeRoute.AddEntries(TreeRouteBuilder
builder, ActionDescriptorCollection actions) at
Microsoft.AspNetCore.Mvc.Internal.AttributeRoute.GetTreeRouter() at
Microsoft.AspNetCore.Mvc.Internal.AttributeRoute.RouteAsync(RouteContext
context) at
Microsoft.AspNetCore.Routing.RouteCollection.d__9.MoveNext()
--- End of stack trace from previous location where exception was thrown --- at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) at
Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown --- at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) at
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.d__7.MoveNext()
Question
Note: the folders are nested like this because there are quite a few pages to the site, so keeping things organised is quite important.
How can I avoid this error? Do I have to change the URL structure - this has proven fairly readable (don't normally use ID numbers)? Does anyone have any examples of a similar nested structure (I have had difficulty in finding examples)?

Related

CloudBlobDirectory' does not contain a definition for 'ListBlobs' and no accessible extension method 'ListBlobs' in .net core 3.1 upgrade

I am upgrading a .net45 app to .net core 3.1 and I have a piece of code there like below.
private void GetContainerDirectories(IEnumerable<IListBlobItem> blobList)
{
// First list all the actual FILES within
// the current blob list. No recursion needed:
foreach (var item in blobList.Where
((blobItem, type) => blobItem is CloudBlockBlob))
{
var blobFile = item as CloudBlockBlob;
sb.Add(new Tree { Name = blobFile.Name, Id = blobFile.Name, ParentId = blobFile.Parent.Prefix, Title = Path.GetFileName(blobFile.Name), IsDirectory = false });
}
// List all additional subdirectories
// in the current directory, and call recursively:
foreach (var item in blobList.Where
((blobItem, type) => blobItem is CloudBlobDirectory))
{
var directory = item as CloudBlobDirectory;
sb.Add(new Tree { Name = directory.Prefix, Id = directory.Prefix, ParentId = directory.Parent.Prefix, Title = new DirectoryInfo(directory.Prefix).Name, IsDirectory = true });
// Call this method recursively to retrieve subdirectories within the current:
GetContainerDirectories(directory.ListBlobs()); ***////////Here i am getting error***
}
}
In the last line [ GetContainerDirectories(directory.ListBlobs()) ], I am getting error for ListBlobs and I am not able to find any useful solution for this. The error like this -
'CloudBlobDirectory' does not contain a definition for 'ListBlobs' and no accessible extension method 'ListBlobs' accepting a first argument of type 'CloudBlobDirectory' could be found (are you missing a using directive or an assembly reference?)
Has anyone any idea how to fix this ? Many thanks in advance :)
The WindowsAzure.Storage SDK you are using is too old, .net core does not support the synchronous methods under this SDK, and the ListBlobs method is a synchronous method.
I suggest you use the latest SDK instead:
https://www.nuget.org/packages/Azure.Storage.Blobs/12.8.0
If you don't want to use Azure.Storage.Blobs SDK, you can use ListBlobsSegmentedAsync method under WindowsAzure.Storage SDK
Update:
You can use the code below to instead of your original code:
var blobs = directory.ListBlobsSegmentedAsync(false, BlobListingDetails.Metadata, 100, null, null, null).Result.Results;
GetContainerDirectories(blobs);

Exception -"Cannot send a content-body with this verb-type" using httpclient.sendasync

Already tried searching SO for similar issues but those didn't help to resolve.
Scenario:
Have a Web Client app which has a Web API app as a backend. There is no issue when calling the same URI if it's from this Client - Web API but the exception happens when I'm trying to trigger the API request from another WebAPI Services application ( say its a separate project for accessing reports which run as a service using Telerik Reporting).
Code:
private async Task<HttpStatusCode> AccessService()
{
HttpResponseMessage response = null;
try
{
using (HttpRequestMessage request = new HttpRequestMessage()
{
Content = new System.Net.Http.StringContent(JSONValues, System.Text.Encoding.UTF8, "application/json"),
RequestUri = new Uri(UriString),
Method = CallMethod
})
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Utils.Token) ;
response = await httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string httpResponse = await response.Content.ReadAsStringAsync();
httpString = JsonConvert.DeserializeObject<string[]>(httpResponse);
}
ErrorCode = response.StatusCode;
response.Dispose();
}
}
catch (Exception ex)
{
Misc.Error= ex.Message;
}
finally
{
response.Dispose();
}
return ErrorCode;
}
Exception:
Cannot send a content-body with this verb-type.
at System.Net.HttpWebRequest.CheckProtocol(Boolean onRequestStream)
at System.Net.HttpWebRequest.BeginGetRequestStream(AsyncCallback callback, Object state)
at System.Net.Http.HttpClientHandler.StartGettingRequestStream(RequestState state)
at System.Net.Http.HttpClientHandler.PrepareAndStartContentUpload(RequestState state)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Net.Http.HttpClient.<FinishSendAsyncBuffered>d__58.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
This works fine when I access the same from the Client app ( asp.net mvc ) and issue happens when I call this from the Report Services API app. Is this has something to with it.
Edit:
The Web client Project is asp.net mvc core 2.1, so I think it works while using content in GET method of HttpClient.SendAsync but the Reports Services App where this issue happens are targets .net 461 .
I strongly doubt this might be the cause since .net framework doesn't allow content for GET operations? Any work arounds?
Link
TIA
Based on the description provided, if you use get method you usually don't have a body in the get request. For more details, please refer to the solutions below:
http://stackoverflow.com/questions/3981564/cannot-send-a-content-body-with-this-verb-type

Upload a text file as attachment using Bot framework not working in Skype channel

I am unable to upload a text file as an attachment using the Bot framework (Bot.Builder v3.11.0), when trying in the Skype channel. It worked in the Bot framework emulator though. Following is the code which uploads a file and to returns the activity with the uploaded file URL in the attachment. It throws an exception when using the Skype channel. Alternatively is there any other way to achieve uploading/attaching a text file in the Skype channel which the user can then download from within the client?
public static async Task<Activity> GetTextAttachmentAsync(Activity message)
{
var reply = message.CreateReply("Here is a text attachment");
var serviceUrl = reply.ServiceUrl;
var conversationId = reply.Conversation.Id;
byte[] fileData = null;
using (var wc = new System.Net.WebClient())
fileData = wc.DownloadData("https://textfiles.com/100/adventur.txt");
using (var connector = new ConnectorClient(new Uri(serviceUrl)))
{
var attachments = new Attachments(connector);
var token = await (connector.Credentials as MicrosoftAppCredentials).GetTokenAsync();
connector.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await attachments.Client.Conversations.UploadAttachmentAsync(
conversationId,
new AttachmentData
{
Name = "transcript.html",
OriginalBase64 = fileData,
Type = "text/html"
});
reply.Attachments = new List<Attachment>
{
new Attachment
{
Name = "transcript.html",
ContentType = "text/html",
ContentUrl = attachments.GetAttachmentUri(response.Id)
}
};
return reply;
}
}
Exception thrown from the UploadAttachmentAsync() function above:
Microsoft.Rest.HttpOperationException: Not Found
at Microsoft.Bot.Connector.ErrorHandling.<HandleErrorAsync>d__2`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Bot.Connector.ConversationsExtensions.<UploadAttachmentAsync>d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Support.Services.Bot.Core.Utilities.AdaptiveCardsHelper.<GetTextAttachmentAsync>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Support.Services.Bot.Core.Dialogs.BotDialog.<HandleMessageAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Support.Services.Bot.Core.Dialogs.DialogBase`1.<MessageReceivedAsync>d__8.MoveNext()
When running your code with Common Language Runtime Errors enabled in Visual Studio. I got the following error:
It seems that the site address https://textfiles.com/100/adventur.txt does not have a trusted certificate and that makes .NET unhappy. When I went to investigate, I found this Stack Overflow answer that suggested using this code below to overcome this but is strongly recommended not to use this in production.
ServicePointManager.ServerCertificateValidationCallback += (o, c, ch, er) => true;
This was going to be my original suggestion before I ran your code: many channels (Skype included), limit the types of files you are able to send from a bot when using base64 and/or local files. For example, I know that you cannot send PDF files as Base64 in Skype. If memory serves correctly, you can only send image and video files (maybe audio too) using the base64 method in Skype. So even if you resolve this error you may run into this afterward. The workaround for this is using hosted files. I'm not exactly sure what you are trying to do with your bot, so I'm not sure if this is an option for you specifically, but it is an option.
So if you find this code not working after resolving the certificate issue, try sending an image file and see if that works, if it does but your HTML file still fails, you will know this is why.

IBM Lotus Notes Domino DLL

The Domino interop API which is included with Lotus Notes causes an out of memory exception in .NET when the NotesDXLExporter class based object fails to export the 390th record, which is a big document, after exporting 389 records (which are smaller documents).
Here is a code snippet:
I initialize the NotesDXLExporter class.
NotesDXLExporter dxl1 = null;
I then configure the NotesDXLExported object as shown below:
dxl1 = notesSession.CreateDXLExporter();
dxl1.ExitOnFirstFatalError = false;
dxl1.ConvertNotesbitmapsToGIF = true;
dxl1.OutputDOCTYPE = false;
I then perform a for a loop shown below in reading documents using the dxl1 class (line on which exception occurs is indicated below).
NotesView vincr = database.GetView(#"(AllIssuesView)"); //view from an NSF file
for (int i = 1; i < vincr.EntryCount; i++)
{
try
{
vincrdoc = vincr.GetNthDocument(i);
System.IO.File.WriteAllText(#"C:\Temp\" + i + #".txt", dxl1.Export(vincrdoc)); //OUT OF MEMORY EXCEPTION HAPPENS HERE WHEN READING A BIG DOCUMENT.
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
I have tried using a different version of the Interop domino dll and had had no success.
As I understand this, I see an API issue but I dont know if I am missing something?
Can you please shed some light on this?
Thanks in advance.
Subbu
You haven't said what version of the Lotus Notes you are working with. Given the history of DXL, I would say that you should try your code on the latest version of Notes that you possibly can.
But also, I don't see any calls to recycle(). Failure to call recycle() for Domino objects causes memory to leak from the Domino back end classes, and since you are running out of memory it could be contributing to your problem. You should also not use a for loop and getNthDocument. You should use getFirstDocument and a while loop with getNextDocument. You'll get much better performance. Putting these two things together leads you to the common pattern of using a temporary document to hold the result of getNextDocument, allowing you to recycle the current document, and then assign the temp document to the current, which would be something like this (not error-checked!)
NotesView vincr = database.GetView(#"(AllIssuesView)"); //view from an NSF file
vincrdoc = vincr.getFirstDocument();
while (vincrdoc != null)
{
try {
System.IO.File.WriteAllText(#"C:\Temp\" + i + #".txt", dxl1.Export(vincrdoc));
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
Document nextDoc = vincr.getNextDocument(vincrdoc);
vincrdoc.recycle();
vincrdoc = nextDoc;
}

How to check if a Document Library (SPDocumentLibrary) supports a particular ContentType

I have a SharePoint 2010 site set up locally for debugging with the following topography:
Main (SPSite)
-> Toolbox (SPWeb)
-> MyTool (SPWeb)
I have created and deployed the following to Main:
Custom Field "RequestedBy"
Custom Field "OriginalRequestFileName"
Custom Content Type "RequestContentType" that contains the above two fields in addition to OOB fields
Custom List Definition "RequestListDefinition" based on the above ContentType
VisualWebPart "MyFileUploaderWebPart" that has a custom EditorPart to allow the user to define which document library the file should be uploaded to.
I have created an instance of a list "My Request List" in MyTool that's based on my custom list definition "RequestListDefinition".
In the EditorPart I've got a drop-down list of document libraries.
private void PopulateDocumentLibraryList(DropDownList dropDownList)
{
SPWeb currentWebsite = SPContext.Current.Web;
SPListCollection lists = currentWebsite.GetListsOfType(SPBaseType.DocumentLibrary);
if (lists.Count > 0)
{
List<SPDocumentLibrary> docLibraries = lists.Cast<SPList>()
.Select(list => list as SPDocumentLibrary)
.Where(library => library != null && !library.IsCatalog && !library.IsSiteAssetsLibrary)
.ToList();
dropDownList.DataSource = docLibraries;
dropDownList.DataTextField = "Title";
dropDownList.DataValueField = "ID";
dropDownList.DataBind();
// Default the selected item to the first entry
dropDownList.SelectedIndex = 0;
}
}
I would like to restrict the list of document libraries to only those that are derived from my custom list definition that I've deployed. I thought of doing this by checking the supported content types and thus tried altering the Where clause to:
private void PopulateDocumentLibraryList(DropDownList dropDownList)
{
SPWeb currentWebsite = SPContext.Current.Web;
SPListCollection lists = currentWebsite.GetListsOfType(SPBaseType.DocumentLibrary);
if (lists.Count > 0)
{
SPContentType voucherRequestListContentType = currentWebsite.ContentTypes["VoucherRequestContentType"];
List<SPDocumentLibrary> docLibraries = lists.Cast<SPList>()
.Select(list => list as SPDocumentLibrary)
.Where(library => library != null && !library.IsCatalog && !library.IsSiteAssetsLibrary && library.IsContentTypeAllowed(voucherRequestListContentType))
.ToList();
dropDownList.DataSource = docLibraries;
dropDownList.DataTextField = "Title";
dropDownList.DataValueField = "ID";
dropDownList.DataBind();
// Default the selected item to the first entry
dropDownList.SelectedIndex = 0;
}
}
It bombs out with the following error though:
Server Error in '/' Application.
--------------------------------------------------------------------------------
Value cannot be null.
Parameter name: ct
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: ct
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[ArgumentNullException: Value cannot be null.
Parameter name: ct]
Microsoft.SharePoint.SPList.IsContentTypeAllowed(SPContentType ct) +26981638
Dominos.OLO.WebParts.FileUploader.<>c__DisplayClass7.<PopulateDocumentLibraryList>b__4(SPDocumentLibrary library) +137
System.Linq.WhereEnumerableIterator`1.MoveNext() +269
System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) +578
System.Linq.Enumerable.ToList(IEnumerable`1 source) +78
Dominos.OLO.WebParts.FileUploader.DocumentLibrarySelectorEditorPart.PopulateDocumentLibraryList(DropDownList dropDownList) +801
Dominos.OLO.WebParts.FileUploader.DocumentLibrarySelectorEditorPart.CreateChildControls() +154
System.Web.UI.Control.EnsureChildControls() +146
Dominos.OLO.WebParts.FileUploader.DocumentLibrarySelectorEditorPart.SyncChanges() +102
Microsoft.SharePoint.WebPartPages.ToolPane.OnSelectedWebPartChanged(Object sender, WebPartEventArgs e) +283
System.Web.UI.WebControls.WebParts.WebPartEventHandler.Invoke(Object sender, WebPartEventArgs e) +0
Microsoft.SharePoint.WebPartPages.SPWebPartManager.BeginWebPartEditing(WebPart webPart) +96
Microsoft.SharePoint.WebPartPages.SPWebPartManager.ShowToolPaneIfNecessary() +579
Microsoft.SharePoint.WebPartPages.SPWebPartManager.OnPageInitComplete(Object sender, EventArgs e) +296
System.EventHandler.Invoke(Object sender, EventArgs e) +0
System.Web.UI.Page.OnInitComplete(EventArgs e) +11056990
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1674
This suggests to me that it's failing to find the content type.
Another thought I had was to try and retrieve all lists that are of my custom list definition type "RequestListDefinition". However, SPWeb.GetListsOfType() takes an SPListTemplateType, which is an enum and thus doesn't contain my custom list definition. The documentation for SPListTemplateType (http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splisttemplatetype.aspx) suggests using a method that accepts a string or an int instead of SPListTemplateType but I haven't seen any documentation for this.
Can someone please help me work out either:
how I can get just those lists that are derived from my custom list definition; or
how I can get a hold of my custom content type; or
point me in the direction of a better solution for restricting the list of SPDocumentLibrary?
Thanks!!
Point 2:
The SPContentType should be retrieved via currentWebsite.AvailableContentTypes[name]. The ContentTypes property of a SPWeb does only return content types created on this particular web. However, AvailableContentTypes does return all content types available in the current site collection.
Update:
To check whether the list has your content type, you should use the content type collection on the list:
SPContentTypeId ctId = voucherRequestListContentType.Id;
// LINQ where clause:
.Where(library => (...) && library.ContentTypes[ctID] != null);
The method SPList.IsContentTypeAllowed checks if a given content type is supported on the list and not if the content type is part of the list. See the MSDN documentation SPList.IsContentTypeAllowed Method.
I found that IsApplicationList (SP 2013) was helpful in limiting to Document Libraries that were non-system libraries (i.e. IsApplicationList is true for _catalogs, SiteAssets, and SitePages, but not for Shared Documents).
In PowerShell, you can see this by running the following
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
Add-PsSnapin Microsoft.SharePoint.PowerShell
$site = Get-SPSite "http://sharePoint2013Url"
$webs = $site.AllWebs
foreach($web in $webs)
{
Write-Host "$($web.Url)"
foreach($list in $web.GetListsOfType([Microsoft.SharePoint.SPBaseType]::DocumentLibrary))
{
Write-Host "$($list.DefaultEditFormUrl) $($list.IsApplicationList)"
}
}

Resources