How to use JSON.NET to serialize an array of objects - asp.net

I remember that one of my friends told me that I can throw anything into JSON.NET and serialize them into JSON format.
public string GetRecords(string apiKey, DateTime start, DateTime end)
{
var user = db.Users.SingleOrDefault(u => u.Id == apiKey);
if (user == null)
{
return string.Empty;
}
var records = db.Histories.Where(h => h.Date >= start && h.Date <= end);
JavaScriptSerializer s = new JavaScriptSerializer();
return JsonConvert.SerializeObject(records);
}
But now I got an exception:
There is already an open DataReader associated with this Command which
must be closed first.
What can I do to resolve this?

Call .ToList() on records, before passing it to JsonConvert.SerializeObject

You probably didn't enable multiple active result sets (MARS) in your config file.
Follow this link
Basically need to add
"MultipleActiveResultSets=True"
Or you could eager load as suggested by 3dd
More help here There is already an open DataReader associated with this Command which must be closed first

Related

How to use "Azure storage blobs" for POST method in controller

I am creating an app where user can upload their text file and find out about its most used word.
I have tried to follow this doc to get used to the idea of using AZURE STORAGE BLOBS - https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet
But I am super newbie and having a hard time figuring it out how to adapt those blobs methods for my POST method.
This my sudo - what I think I need in my controller and what needs to happen when POST method is triggered.
a.No need for DELETE or PUT, not replacing the data nor deleting in this app
b.Maybe need a GET method, but as soon as POST method is triggered, it should pass the text context to the FE component
POST method
connect with azure storage account
if it is a first time of POST, create a container to store the text file
a. how can I connect with the existing container if the new container has already been made? I found this, but this is for the old CloudBlobContainer. Not the new SDK 12 version.
.GetContainerReference($"{containerName}");
upload the text file to the container
get the chosen file's text content and return
And here is my controller.
public class HomeController : Controller
{
private IConfiguration _configuration;
public HomeController(IConfiguration Configuration)
{
_configuration = Configuration;
}
public IActionResult Index()
{
return View();
}
[HttpPost("UploadText")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
if (files != null)
{
try
{
string connectionString = Environment.GetEnvironmentVariable("AZURE_STORAGE_CONNECTION_STRING");
BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
string containerName = "textdata" + Guid.NewGuid().ToString();
BlobContainerClient containerClient = await blobServiceClient.CreateBlobContainerAsync(containerName);
//Q. How to write a if condition here so if the POST method has already triggered and container already created, just upload the data. Do not create a new container?
string fileName = //Q. how to get the chosen file name and replace with newly assignmed name?
string localFilePath = //Q. how to get the local file path so I can pass on to the FileStream?
BlobClient blobClient = containerClient.GetBlobClient(fileName);
using FileStream uploadFileStream = System.IO.File.OpenRead(localFilePath);
await blobClient.UploadAsync(uploadFileStream, true);
uploadFileStream.Close();
string data = System.IO.File.ReadAllText(localFilePath, Encoding.UTF8);
//Q. If I use fetch('Home').then... from FE component, will it receive this data? in which form will it receive? JSON?
return Content(data);
}
catch
{
//Q. how to use storageExeption for the error messages
}
finally
{
//Q. what is suitable to execute in finally? return the Content(data) here?
if (files != null)
{
//files.Close();
}
}
}
//Q. what to pass on inside of the Ok() in this scenario?
return Ok();
}
}
Q1. How can I check if the POST method has been already triggered, and created the Container? If so how can I get the container name and connect to it?
Q2. Should I give a new assigned name to the chosen file? How can I do so?
Q3. How can I get the chosen file's name so I can pass in order to process Q2?
Q4. How to get the local file path so I can pass on to the FileStream?
Q5. How to return the Content data and pass to the FE? by using fetch('Home').then... like this?
Q6. How can I use storageExeption for the error messages
Q7. What is suitable to execute in finally? return the Content(data) here?
Q8. What to pass on inside of the Ok() in this scenario?
Any help is welcomed! I know I asked a lot of Qs here. Thanks a lot!
Update: add a sample code, you can modify it as per your need.
[HttpPost]
public async Task<IActionResult> SaveFile(List<IFormFile> files)
{
if (files == null || files.Count == 0) return Content("file not selected");
string connectionString = "xxxxxxxx";
BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
string containerName = "textdata" + Guid.NewGuid().ToString();;
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
containerClient.CreateIfNotExists();
foreach (var file in files)
{
//use this line of code to get file name
string fileName = Path.GetFileName(file.FileName);
BlobClient blobClient = containerClient.GetBlobClient(fileName);
//directly read file content
using (var stream = file.OpenReadStream())
{
await blobClient.UploadAsync(stream);
}
}
//other code
return View();
}
Original answer:
When using List<IFormFile>, you should use foreach code block to iterate each file in the list.
Q2. Should I give a new assigned name to the chosen file? How can I do
so?
If you want to keep the file original name, in the foreach statement like below:
foreach (var file in myfiles)
{
Path.GetFileName(file.FileName)
//other code
}
And if you want to assign a new file name when uploaded to blob storage, you should define the new name in this line of code: BlobClient blobClient = containerClient.GetBlobClient("the new file name").
Q3. How can I get the chosen file's name so I can pass in order to
process Q2?
refer to Q2.
Q4. How to get the local file path so I can pass on to the FileStream?
You can use code like this: string localFilePath = file.FileName; to get the path, and then combine with the file name. But there is a better way, you can directly use this line of code Stream uploadFileStream = file.OpenReadStream().
Q5. How to return the Content data and pass to the FE? by using
fetch('Home').then... like this?
Not clear what's it meaning. Can you provide more details?
Q6. How can I use storageExeption for the error messages
The storageExeption does not exist in the latest version, you should install the older one.
You can refer to this link for more details.
#Ivan's answer is what the documentation seems the recommend; however, I was having a strange issue where my stream was always prematurely closed before the upload had time to complete. To anyone else who might run into this problem, going the BinaryData route helped me. Here's what that looks like:
await using var ms = new MemoryStream();
await file.CopyToAsync(ms);
var data = new BinaryData(ms.ToArray());
await blobClient.UploadAsync(data);

Get Guid with query string

I get a Guid value in a variable like that
var getvalueGuid = db.Clients.Where(u => u.Numero_telephone ==
TextBox_numero_telephone.Text).Select(u => u.GuID).FirstOrDefault();
And i would like to convert it in a query string like that:
getvalueGuid = Request.QueryString["id"];
How to do?
You can use Guid.TryParse:
Guid getvalueGuid;
if(Guid.TryParse(Request.QueryString["id"], out getvalueGuid))
{
// successfully parsed
}
You will be able to get that inside a QueryString, only if you're having the url like
www.example.com/page?id=[guid_here]
Then when you'll use the code, it would provide you with a String which would contain the Query String provided in the URL.
It's hard to understand your question as you're missing a lot of detail, but I think you want to get a strongly-typed GUID value from the querystring?
System.Guid doesn't have a TryParse method, so you'll have to use the constructor and catch any exceptions thrown:
If so, then do this:
String guidStr = Request.QueryString["id"];
Guid guid = null;
try {
guid = new Guid( guidStr );
} catch(ArgumentNullException) {
} catch(FormatException) {
} catch(OverflowException) {
}
if( guid == null {
// Inform user that the GUID specified was not valid.
}
The three exceptions (ArgumentNullException, FormatException, and OverflowException are documented in the notes for the Guid(String) constructor here: http://msdn.microsoft.com/en-us/library/96ff78dc%28v=vs.110%29.aspx
Update:
I forgot that .NET 4.0 introduced the TryParse method. Use that instead if you're using .NET 4.0 or later: http://msdn.microsoft.com/en-us/library/system.guid.tryparse%28v=vs.110%29.aspx
Guid requestGuid;
if (Guid.TryParse(Request.QueryString["id"], out requestGuid))
{
// Do logic here with requestGuid
}

Using a custom method in LINQ to Entities

I have a LINQ expression:
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => s.User).Distinct().Select(s => new UserViewModel() {
Username = s.Username,
LastActiveDateTime = s.LastActive, // DateTime, I want it to be a string filtered through my custom GetFriendlyDate method
}).ToList();
// convert all DateTimes - yuck!
foreach (var userViewModel in users) {
userViewModel.LastActive = DateFriendly.GetFriendlyDate(userViewModel.LastActiveDateTime);
}
This solution is working, but it feels wrong to
have to iterate over all users after getting them from the db, just to reformat a property on every single one
have a DateTime property on my ViewModel just so that it can later be converted to a string and never touched again
Is there any way I can use the GetFriendlyDate method directly within the query?
Possible solutions, worth to mention:
Have a getter property of your ViewModel, which would return transformed string, something like:
public string LastActive
{
get
{
return DateFriendly.GetFriendlyDate(LastActiveDateTime);
}
}
Though it not solves your problem with existing LastActiveDateTime column, transformation will be applied only at moment of usage (in your view, most likely - anyways if you will try use it somewhere latter in query, it will not work for reason you already know), so no need to iterate manually.
Create View, which will transform data on server side; so your data will already be returned in format you need, if you're using DBFirst, probably, it's easiest and fastest solution;
Finally, you can use ToList() twice, once before selecting new ViewModel() (or call AsEnumerable(), or find other way to materialize query). It will fetch data from database and will allow you perform any C#-side functions you want directly in query after ToList(). But, as mentioned before - it's about getting all data, which matched criteria up to ToList() - in most cases it's not appropriate solution.
And here is some additional readings:
How can I call local method in Linq to Entities query?
I tested it in LINQpad and it works.
It still kinda iterates over users (with LINQ) but you don't have to add DateTime property to your viewmodel class. Also you could convert collection of Users to collection of UserViewModel objects with Automapper. It would still iterate over users of course but you wouldn't see it.
Had to create some setup code of course because I don't have your database.
void Main()
{
var db = new List<User> {
new User { LastActive = DateTime.Now, Username = "Adam", Lastname = "Nowak" },
new User { LastActive = DateTime.Now.AddYears(1), Username = "Eve", Lastname = "Kowalska"}
};
// select only properties that you need from database
var users = db
.Select(user => new { Username = user.Username, LastActive = user.LastActive})
.Distinct()
.ToList();
var usersVM = from u in users
select new UserViewModel { Username = u.Username, LastActiveDateTime = DateFriendly.GetFriendlyDate(u.LastActive)};
usersVM.Dump();
}
class User
{
public DateTime LastActive;
public string Username;
public string Lastname;
};
class UserViewModel
{
public string Username;
public string LastActiveDateTime;
}
static class DateFriendly
{
public static string GetFriendlyDate(DateTime date)
{
return "friendly date " + date.Year;
}
}
And this outputs
Username LastActiveDateTime
Adam friendly date 2013
Eve friendly date 2014
There is no direct Concert.ToDate method available for LINQ. But you can try using the DateAdd method from the SqlFunctions class:
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => new
{
s.User.Username,
LastActive=SqlFunctions.DateAdd("d",0, s.LastActive)
})
.ToList().Select(s => new UserViewModel()
{
Username = s.Username,
LastActiveDateTime = s.LastActive
});
Wouldn't the following work?
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => s.User).Distinct().Select(s => new UserViewModel() {
Username = s.Username,
LastActiveDateTime = DateFriendly.GetFriendlyDate(s.LastActive)
}).ToList();

assign out parameter totalRecords when using caching

I'm using asp.net Sqlmembership.GetAll() method ( paging overload ) . now I want to add a caching layer to cache results but there is problem with GetAll method's out parameter that returns the total number of records . how can I assign a value to totalRecords parameter when data are retrieved from cache ?
If my understanding is correct this little flow will help you to achieve your goal
This is a basic flow:
When you want to access the cached object, ask it to the cache provider
If the object is not null, then cast to the correct type and return the object from the cache (in this case, the list of users). End process
If the object is null then
Retrieve the object from its original source (using the GetAll methods)
Save the retrieved object to the cache
Return the retrieved object. End process
In any case, I would recommend you to work with a custom domain class instead of the MembershipUser class
This is a basic example:
public IEnumerable<DomainUser> GetDomainUsers()
{
var context = HttpContext.Current;
var cache = context.Cache;
var domainUsers = cache["domainUsers"] as IEnumerable<DomainUser>;
if (domainUsers == null)
{
domainUsers = Membership.GetAllUsers().OfType<MembershipUser>().Select(x => new DomainUser
{
Email = x.Email,
Username = x.UserName
});
cache.Insert(
"domainUsers", // cache key
domainUsers, // object to cache
null, // dependencies
DateTime.Now.AddMinutes(30), // absoulute expiration
Cache.NoSlidingExpiration, // slading expiration
CacheItemPriority.High, // cache item priority
null // callback called when the cache item is removed
);
context.Trace.Warn("Data retrieved from its original source");
}
else
{
context.Trace.Warn("Data retrieved from cache");
}
return domainUsers;
}

Asp.net MVC LINQ error message

Using the ASP.NET Web-Api, I have the following POST setup in my controller. When posting to it from Fiddler, I get the error message:
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities.
...when it gets to the var auth = dba.ApiMembers... line
// POST api/Avail
[BasicAuthentication]
public HttpResponseMessage PostAvail(Avail[] avail)
{
if (ModelState.IsValid)
{
// Check if authorised
var auth = dba.ApiMembers.Where(a => a.hotel_id ==
avail[0].HID && a.UserName == User.Identity.Name)
.FirstOrDefault();
Can anyone see anything wrong with this line?
LINQ2SQL queries run in database, and it's not possible to translate User.Identity.Name and avail[0] into database commands. You should initialize those values as parameters and pass simple types to LINQ query.
var hid = avail[0].HID;
var userName = User.Identity.Name;
var auth = dba.ApiMembers.Where(a => a.hotel_id == hid && a.UserName == userName).FirstOrDefault();

Resources