Azure Storage FileShare with ASP.Net Core and Razor Page - asp.net-core

Beginner question
I am using the latest ASP.NET Core SDK to build an MVC/Razor page to display user files. We have the files in Azure Storage FileShare. The difficulty I'm running into is getting the FILES to list out and there is very little documentation on how to do this out there. Once I finally get it figured out I would like to create a post on Medium or somewhere else to help any other beginners.
The file structure of the File Share is as such:
Azure File Share
MainShare
EmployeNumber
Folder1
files.pdf
Folder2
files.pdf
Folder3
files.pdf
I have been able to successfully get blob information to display since that's the bulk of information out there, but I'm having trouble FileShare to display anything.
At first, I was running into an invalid cast issue where it was trying to cast CloudFileDirectory on my CloudFile so I found a solution to help it determine what to cast where. Now the page will attempt to run but nothing is produced and the page just loads and loads.
FileController.cs
public async Task<IActionResult> Index()
{
string filestorageconnection = _configuration.GetValue<string>("filestorage");
CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(filestorageconnection);
//CloudFile cloudFile = null;
CloudFileShare fileShare = null;
CloudFileClient cloudFileClient = cloudStorageAccount.CreateCloudFileClient();
fileShare = cloudFileClient.GetShareReference("MainShare");
List<IListFileItem> shareData = new List<IListFileItem>();
List<FileShareData> fileData = new List<FileShareData>();
FileContinuationToken token = null;
do
{
FileResultSegment resultSegment =
await fileShare.GetRootDirectoryReference().ListFilesAndDirectoriesSegmentedAsync(token);
foreach (var fileItem in resultSegment.Results)
{
if (fileItem is CloudFile)
{
var cloudFile = (CloudFile) fileItem;
//await cloudFile.FetchAttributesAsync(); <--- Not sure this does what i'm looking for
// Add properties to FileShareData List
fileData.Add(new FileShareData()
{
FileName = cloudFile.Name,
LastModified = DateTime.Parse(cloudFile.Properties.LastModified.ToString()).ToLocalTime().ToString(),
Size = Math.Round((cloudFile.Properties.Length / 1024f) / 1024f, 2).ToString()
});
}
else if (fileItem is CloudFileDirectory)
{
var cloudFileDirectory = (CloudFileDirectory) fileItem;
await cloudFileDirectory.FetchAttributesAsync();
}
}
} while (token != null);
return View(fileData);
}
FileShareData.cs
namespace FileShareMVC.Models
{
public class FileShareData
{
public string FileName { get; set; }
public string LastModified { get; set; }
public string Size { get; set; }
}
}
ShowAllFiles.cshtml
#model List<FileShareData>
#{
ViewData["Title"] = "ShowAllFiles";
}
<h1>ShowAllBlobs</h1>
<table class="table table-bordered">
<thead>
<tr>
<th>FileName</th>
<th>FileSize</th>
<th>ModifiedOn</th>
<th>Download</th>
</tr>
</thead>
<tbody>
#foreach (var data in Model)
{
<tr>
<td>#data.FileName</td>
<td>#data.Size</td>
<td>#data.LastModified</td>
<td> Download </td>
</tr>
}
</tbody>
</table>
I'm not sure where to set a breakpoint to see whats stalling where. I've looked at the Network files in chrome when loading the page but nothing populates either.
Suggestions?

Regarding how to list all files in one Azure file share with method ListFilesAndDirectoriesSegmentedAsync, please refer to the following code
The file structure of the File Share is as such:
Azure File Share
MainShare
mydirectory
logs
STATS.LOG
csv
test.csv
cert
examplecert.pfx
The SDK I use
<PackageReference Include="Microsoft.Azure.Storage.File" Version="11.1.7" />
Code
FileController.cs
public class FileController : Controller
{
public async Task<IActionResult> Index()
{
string accountName = "blobstorage0516";
string key = "eGier5YJBzr5z3xgOJUb+snTGDKhwPBJRFqb2nL5lcacmKZXHgY+LjmYapIHL7Csvgx75NwiOZE7kYLJfLqWBg==";
var storageAccount = new CloudStorageAccount(new StorageCredentials(accountName, key), true);
var share = storageAccount.CreateCloudFileClient().GetShareReference("mainshare");
var dir =share.GetRootDirectoryReference();
//list all files in the directory
var fileData = await list_subdir(dir);
return View(fileData);
}
private static async Task<List<FileShareData>> list_subdir(CloudFileDirectory fileDirectory)
{
var fileData = new List<FileShareData>();
FileContinuationToken token = null;
do
{
FileResultSegment resultSegment = await fileDirectory.ListFilesAndDirectoriesSegmentedAsync(token);
foreach (var fileItem in resultSegment.Results) {
if (fileItem is CloudFile) {
var cloudFile = (CloudFile)fileItem;
//get the cloudfile's propertities and metadata
await cloudFile.FetchAttributesAsync();
// Add properties to FileShareData List
fileData.Add(new FileShareData()
{
FileName = cloudFile.Name,
LastModified = DateTime.Parse(cloudFile.Properties.LastModified.ToString()).ToLocalTime().ToString(),
// get file size as kb
Size = Math.Round((cloudFile.Properties.Length / 1024f), 2).ToString()
});
}
if (fileItem is CloudFileDirectory)
{
var cloudFileDirectory = (CloudFileDirectory)fileItem;
await cloudFileDirectory.FetchAttributesAsync();
//list files in the directory
var result = await list_subdir(cloudFileDirectory);
fileData.AddRange(result);
}
}
// get the FileContinuationToken to check if we need to stop the loop
token = resultSegment.ContinuationToken;
}
while (token != null);
return fileData;
}
}
FileShareData.cs
public class FileShareData
{
public string FileName { get; set; }
public string LastModified { get; set; }
public string Size { get; set; }
}
ShowAllFiles.cshtml
#model List<FileShareData>
#{
ViewData["Title"] = "ShowAllFiles";
}
<h1>ShowAllBlobs</h1>
<table class="table table-bordered">
<thead>
<tr>
<th>FileName</th>
<th>FileSize</th>
<th>ModifiedOn</th>
<th>Download</th>
</tr>
</thead>
<tbody>
#foreach (var data in Model)
{
<tr>
<td>#data.FileName</td>
<td>#data.Size</td>
<td>#data.LastModified</td>
<td> Download </td>
</tr>
}
</tbody>
</table>
Result
For more details, please refer to here and here

Related

Azure OpenID get city

I have a Balzor-sever application that calls the Microsoft Graph API for a signed-in user. I followed Microsoft doc to implement that, my startup page has this code.
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
My initialScopes are User.Read, GroupMember.Read.All.
I want to use AuthenticationStateProvider to get city because when I inject GraphServiceClient as service and tried to use it I get some error, that no one has logged in yet! Something like this
using Microsoft.AspNetCore.Components.Authorization;
public class AuthService : IAuthService
{
private readonly AuthenticationStateProvider authenticationStateProvider;
public AuthService(AuthenticationStateProvider authenticationStateProvider)
{
this.authenticationStateProvider = authenticationStateProvider;
}
public async ValueTask<UserConfiguration> GetUserInfo()
{
//Here I would like to get city also in the claims
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
......
}
}
In my startup, I have this:
services.AddScoped<IAuthService, AuthService>();
My question, how can I get the city of logged in suer?
I tried to add different scopes like "Directory.Read.All" and granted them as application permission and delegated permission but it didn't work.
Thanks.
For Ms graph api get user, it is able to return the city property, but you have to use Odata query parameter select to get it from the api response. By the way, I used sample code from here.
Then in your code, when using graph SDK you should write code like this and it worked for me:
_user = await GraphClient.Me.Request().Select("DisplayName, city").GetAsync();
I have a razor component like this:
#page "/showprofile"
#using Microsoft.AspNetCore.Authorization
#attribute [Authorize]
#inherits UserProfileBase
<h3>User Profile</h3>
#{
<table class="table">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td> DisplayName </td>
<td> #_user.DisplayName </td>
</tr>
<tr>
<td> User city </td>
<td> #_user.City </td>
</tr>
</table>
}
And have a base file UserProfileBase.cs like this:
using Microsoft.AspNetCore.Components;
using Microsoft.Graph;
using Microsoft.Identity.Web;
using System;
using System.Threading.Tasks;
namespace BlazorServerAad.Pages
{
public class UserProfileBase : ComponentBase
{
[Inject]
GraphServiceClient GraphClient { get; set; }
[Inject]
MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler { get; set; }
protected User _user = new User();
protected override async Task OnInitializedAsync()
{
await GetUserProfile();
}
private async Task GetUserProfile()
{
try
{
_user = await GraphClient.Me.Request().Select("DisplayName, city").GetAsync();
var a = "1";
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
ConsentHandler.HandleException(ex);
}
}
}
}

Save all kinds of files with ASP.NET Core application in database [duplicate]

I am working on a small blog using ASP.NET Core(MVC 6) EF Visual Studio. I have trouble finding how to save images to a database. I have read about IFormfile but I do not really understand how to go about it, I am stuck. I am new to this and would love to have a little help.
I want to save the image to the post I am creating(In the same form). I, therefore, want to save it to postID. Then I need to be able to display the image, how do I do that?
You may find this useful if u need to save to database. This was a modification of https://www.mikesdotnetting.com/article/259/asp-net-mvc-5-with-ef-6-working-with-files and lots of input from k7Boys answer here MVC 6 HttpPostedFileBase?
<input type="file" name="Image" id="Imageinput">
Blog Modal Class should have Img field like;
public int BlogId{ get; set; }
...
public byte[] Img{ get; set; }
Controller;
public async Task<IActionResult> Create([Bind("BlogId,...Img")] Blog blog t, IFormFile Image)
if (ModelState.IsValid)
{
if (Image!= null)
{
if (Image.Length > 0)
//Convert Image to byte and save to database
{
byte[] p1 = null;
using (var fs1 = Image.OpenReadStream())
using (var ms1 = new MemoryStream())
{
fs1.CopyTo(ms1);
p1 = ms1.ToArray();
}
Blog.Img= p1;
}
}
_context.Add(client);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
Took me a couple of hours to get here. Now working on viewing the images in a view, am sure this will not be complex. Enjoy
Try this its working fine
controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,PostMode,Message,Image,AccountId,Created,Status")] Post post, IFormFile Image)
{
if (ModelState.IsValid)
{
using (var ms = new MemoryStream())
{
Image.CopyTo(ms);
post.Image = ms.ToArray();
}
_context.Add(post);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(post);
}
Display Image
#foreach (var item in Model)
{
<img class="img-responsive full-width" src="data:image/jpeg;base64,#Convert.ToBase64String(item.Image)" />
}
You can use IFormFile to save image posted from view. Below is the sample code.
public class UserProfileViewModel
{
public string UserName { get; set; }
public IFormFile UploadedImage { get; set; }
public string ImageUrl { get; set; }
}
In view simply bind it with IFormFile property like:
<img src="#Model.ImageUrl" alt="User Logo" asp-append-version="true" />
<input type="file" asp-for="UploadedImage" />
In your controller you just need to save file on server like:
var filename = ContentDispositionHeaderValue
.Parse(user.UploadedImage.ContentDisposition)
.FileName
.Trim('"');
filename = Path.Combine(webRoot, "/Content/UserProfile/", $#"\{filename}");
if (Directory.Exists(webRoot + "/Content/UserProfile/"))
{
using (FileStream fs = System.IO.File.Create(filename))
{
user.UploadedImage.CopyTo(fs);
fs.Flush();
}
}
model.ImageURL = "~/Content/Brands/" + user.UploadedImage.FileName;

How to pass all text of input selected row into action?

i have this view in my project.
I want to get the text of input in each row that is selected.
How to pass all text of input selected row into action
<table width="100%" class="table table-striped table-bordered table-hover" id="dataTables-example">
<thead>
<tr>
<th width="45%">Select</th>
<th width="45%">User Name</th>
<th width="5%">Description</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.TypeList)
{
<tr>
<td>
<input type="checkbox" name=checklist" id="checklist"/>
</td>
<td>
#Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
<input type="text" name="Extradecription"/>
</td>
</tr>
}
</tbody>
my Actions. How can I have the corresponding values of text and checkbox for the Selected rows
public IActionResult Index()
{
return View(repository.GetUser());
}
public IActionResult Save(int[] checklist,string[] Extradecription)
{
repository.Save(checklist,Extradecription);
return View(repository.GetUser());
}
If you try to get two different arrays as you have showed in you controller-action code, there will be a trouble with text for non selected items, the array for check boxes will bind as expected but for descriptions will be different, just to be clear, check the following example:
Assuming We have a list with tree options:
100 - Foo
200 - Bar
300 - Zaz
If We set the following selection for items:
Foo, a
Zaz, c
If We take a look on the request, this is the raw request:
checklist = 100,300
Extradecription = a,null,c
So, the trouble is avoid to bind null descriptions for non selected options, this is complicated, in that case I recommend to you a clear solution:
Create a model to create entity process
Create a model for option
Add a list of option model in create entity model
Initialize the model to create a new entity
Render inputs in view using asp-for tag
Retrieve the request to create a new entity
I'll assume the name of models and properties to show how to bind a typed array in your request, change the names according to your scenario.
Create entity model:
public class CreateEntity
{
public CreateEntity()
{
Items = new List<SelectedItem>();
}
// Step 3
[BindProperty]
public List<SelectedItem> Items { get; set; }
// Another properties
}
Model for option:
public class SelectedItem
{
public bool IsSelected { get; set; }
public int Code { get; set; }
public string Name { get; set; }
public string Desc { get; set; }
}
Rendering the options list:
#for (var i = 0; i < Model.Items.Count; i++)
{
<input asp-for="#Model.Items[i].IsSelected" />#Model.Items[i].Name
<input asp-for="#Model.Items[i].Desc" />
<br/>
}
The GET and POST actions in controller:
[HttpGet]
public IActionResult CreateOption()
{
// Set the items list
var model = new CreateEntity
{
Items = new List<SelectedItem>
{
new SelectedItem{ Code = 100, Name = "Foo" },
new SelectedItem{ Code = 200, Name = "Bar" },
new SelectedItem{ Code = 300, Name = "Zaz" }
}
};
return View(model);
}
[HttpPost]
public IActionResult CreateOption(CreateEntity form)
{
// Retrieve only selected items
var query = form.Items.Where(item => item.IsSelected == true).ToList();
return View();
}
If you want to know more about check boxes in Razor pages, please check this link: Checkboxes in a Razor Pages Form
Please let me know if this answer is useful.

MVC IPagedList with model type error

I get the error in my MVC 5 App:
CS1061: 'IPagedList' does not contain a definition for 'TargetContact' and no extension method 'TargetContact' accepting a first argument of type 'IPagedList' could be found (are you missing a using directive or an assembly reference?)
I saw the answers here but I still don’t get it done :(
It's probably pretty easy to solve.
public ActionResult Index(string searchTargetContact = null, int page = 1)
{
var model =
from r in db.Outreach
orderby r.TargetContact descending
where (r.TargetContact.StartsWith(searchTargetContact) || searchTargetContact == null)
select new Models.OutreachSetListViewModel
{
TargetContact = r.TargetContact,
NextOutreachStep = r.NextOutreachStep,
GoalOfOutreach = r.GoalOfOutreach,
};
model.ToPagedList(page, 10);
return View(model);
namespace WebApplication11.Models
{
public class OutreachSetListViewModel
{
public string NextOutreachStep { get; set; }
public string TargetContact { get; set; }
public string GoalOfOutreach { get; set; }
}
}
#model IPagedList<OutreachSetListViewModel>
<table class="table" id="networkingList">
<tr>
<th>#Html.DisplayNameFor(model => model.TargetContact)</th>
<th>#Html.DisplayNameFor(model => model.NextOutreachStep)</th>
<th>#Html.DisplayNameFor(model => model.GoalOfOutreach)</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.TargetContact)</td>
<td>#Html.DisplayFor(modelItem => item.NextOutreachStep)</td>
<td>#Html.DisplayFor(modelItem => item.GoalOfOutreach)</td>
</tr>
}
The model in the view is IPagedList<OutreachSetListViewModel>, so when you are looping though the model, each item does have a TargetContact.
However, when you are displaying the header, the model for the DisplayNameFor is not the individual item, but the list. The list does not have the TargetContact property so we have to get it from one of the items in the list.
In this case, we check to see if there are any elements in the list, and if there are, get the TargetContact from the first element.
#if(Model.Any())
{
<tr>
<th>#Html.DisplayNameFor(model => model[0].TargetContact)</th>
<th>#Html.DisplayNameFor(model => model[0].NextOutreachStep)</th>
<th>#Html.DisplayNameFor(model => model[0].GoalOfOutreach)</th>
<th></th>
</tr>
}
Controller
You are not doing anything with the returned value from model.ToPagedList(page, 10);
Save it to a value and pass it in to the view:
var vm = model.ToPagedList(page, 10);
return View(vm);
I know i am a late, but then again I was phasing this problem today and solved by using the same methodology used when it's an IEnumerable all I did was replace IEnumerable with IPagedList and it worked like a charm.
public static string DisplayNameFor<TModelItem, TResult>(this IHtmlHelper<IEnumerable<TModelItem>> htmlHelper, Expression<Func<TModelItem, TResult>> expression)
{
if (htmlHelper == null)
throw new ArgumentNullException(nameof(htmlHelper));
if (expression == null)
throw new ArgumentNullException(nameof(expression));
return htmlHelper.DisplayNameForInnerType(expression);
}
public static string DisplayNameFor<TModelItem, TResult>(this IHtmlHelper<IPagedList<TModelItem>> htmlHelper, Expression<Func<TModelItem, TResult>> expression)
{
if (htmlHelper == null)
throw new ArgumentNullException(nameof(htmlHelper));
if (expression == null)
throw new ArgumentNullException(nameof(expression));
return htmlHelper.DisplayNameForInnerType(expression);
}

Binding JSON Data Back to the MVC View to dynamically insert data to a table

I am working on an MVC4 application. In the submit action of the button, i am making a call to a controller action, which will return data back to me as Json. Number of rows in the table is dependent on the number of rows returned by the service.
I have created a new View Model to support this.
public class DataResponse
{
public List<ALSResult> Result { get; set; }
public string ResponseText {get;set;}
}
public class ALSResult
{
public string Name {get;set;}
public string DataResult {get;set;}
}
Controller Action
[HttpPost]
public ActionResult Submit(CViewModel calcModel)
{
try
{
DataResponse response = cRepository.GetDetails(calcModel.SelectedPlan,calcModel.EquipmentPrice,calcModel.DownPayment);
return Json(response, JsonRequestBehavior.DenyGet);
}
}
js file
$("#Submit").click(function () {
other code here
Process("/DPPC/Submit", "POST", JSON.stringify(model), "json", "application/json;charset=utf-8", logError, InvokeSuccess);
}
function InvokeSuccess(result) {
i will get json result in result filed
}
index.cshtml
<table id="tblResults">
<tr>
<td id="tdResultEt"></td>
<td id="tdResultEtPrice"></td>
</tr>
<tr>
<td id="tdResultDt"></td>
<td id="tdResultDtPrice"></td>
</tr>
more rows depending on the number of items in the JSON response
</table>
How do i dynamically bind the response from the Service to create rows for the table and bind the results?
I don't test it, and it maybe contain syntax errors
function InvokeSuccess(result) {
var table = $("#tblResults");
for(var i = 0; i < result.Result.lenght; ++i)
{
var item = result.Result[i];
var newTr = $("<tr>");
var newTd1 = $("<td>");
var newTd2 = $("<td>");
newTd1.text(item.Name);
newTd2.text(item.DataResult);
newTr.append(newTd1);
newTr.append(newTd2);
table.append(newTr);
}
}