I am experimenting with the Microsoft Graph API and I set up a project with the QuickStarts on the Microsoft developers site.
I'm currently having problems when trying to list all email contacts associated with the logged account.
My problem seems to reside on how I'm requesting and passing the information to ViewBag as it throws a NullReference exceptions even with the proper front-end listing.
Here is the relevant code:
HomeController.cs
public async Task<ActionResult> GetMyContacts()
{
try
{
// Init GraphServiceClient
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient();
var contacts = await graphService.GetMyEmailContacts(graphClient);
ViewBag.Contacts = contacts;
return View("Graph");
}
catch (ServiceException se)
{
if (se.Error.Message == Resource.Error_AuthChallengeNeeded) return new EmptyResult();
return RedirectToAction("Index", "Error", new { message = Resource.Error_Message + Request.RawUrl + ": " + se.Error.Message });
}
}
GraphService.cs
public async Task<IList<Contact>> GetMyEmailContacts(GraphServiceClient graphClient)
{
User me = await graphClient.Me.Request().Select("mail,userPrincipalName").GetAsync();
var contacts = me.Contacts;
return contacts;
}
Graph.cshtml
<div class="col-sm-12">
<pre name="user-contacts">#ViewBag.Contacts</pre>
</div>
<h2>TEST GET CONTACTS</h2>
<div class="col-sm-12">
<p>#Html.Raw(ViewBag.Contacts)</p>
</div>
What am I missing here?
In your view, you have to cast it back to the original type. ViewBag is a C# 4 dynamic type. Returned entities are also dynamic unless cast.
var contactList = ViewBag.Contacts as IEnumerable<Contact>;
If you want to display the whole list, it's as simple as this:
#foreach (var contact in contactList)
{
<li>#contact</li>
}
Related
ASP.NET MVC 5 with .NET Framework v4.5.2
I've got a large, existing project that I have been assigned. Some parts of it take a really long time, so I wanted to see about putting some of these "new to me" asynchronous calls in the code.
But, after looking at the examples, I don't know if what I have can do that.
Take this typical method on my controller:
public ActionResult DealerSelect()
{
var model = RetailActivityModelData.GetCurrentDealers(null, ViewBag.Library);
return View(model);
}
I don't really understand how to turn that into an asynchronous call unless I did something like this:
public async Task<ActionResult> DealerSelect()
{
var model = await RetailActivityModelData.GetCurrentDealers(null, ViewBag.Library);
return View(model);
}
But the compiler complains with:
The return type of an async method must be void, Task, Task, a task-like type, IAsyncEnumerable, or IAsyncEnumerator
I'm not sure how to change the return type without destroying the View model.
So, I went into the GetCurrentDealers method to make a basic BackgroundWorker call:
public static DealerModel GetCurrentDealers(DealerModel model, string library)
{
m_library = library;
if (model == null)
{
model = new AdminCurrentDealerModel();
}
using (var bg = new BackgroundWorker())
{
var mre = new ManualResetEvent(false);
bg.DoWork += delegate (object s, DoWorkEventArgs e)
{
// fill 3 DataTables with calls to database
};
bg.ProgressChanged += delegate (object s, ProgressChangedEventArgs e)
{
// add data from DataTables to model
};
bg.RunWorkerCompleted += delegate (object s, RunWorkerCompletedEventArgs e)
{
mre.Set();
};
bg.RunWorkerAsync();
if (bg.IsBusy)
{
mre.WaitOne();
}
}
return model;
}
It compiles just fine, but crashes as soon as bg.RunWorkerAsync() is called:
System.InvalidOperationException: 'An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%# Page Async="true" %>. This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it.'
This is an ASP.NET MVC Razor page, so it doesn't really have that <%# Page Async="true" %> part.
Razor page:
#model Manufacturer.Models.CurrentDealerModel
#{
ViewBag.Title = "DealerSelect";
Layout = "~/Views/Shared/_ConsoleLayout.cshtml";
}
<style>
#DealerInfoGrid{
display:grid;
grid-template-columns:1fr 1fr 1fr;
}
</style>
<h2>Dealer Select</h2>
#{
var dealers = new List<SelectListItem>();
foreach (var dealer in Model.Dealers) {
dealers.Add(new SelectListItem() { Value = dealer.DealerNumber, Text = dealer.DealerName });
}
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken();
#Html.DropDownListFor(m => m.Dealer.DealerNumber,dealers);
<input type="submit" value="Submit" />
}
#{
if (Model.DealershipInfo != null)
{
<div id="DealerInfoGrid">
<div>
<label>Region: #Model.DealershipInfo.Region</label><br />
<label>Name: #Model.DealershipInfo.Name</label><br />
<label>Address: #Model.DealershipInfo.Address</label><br />
</div>
<div>
<label>Dealer No: #Model.DealershipInfo.DealerNumber</label><br />
<label>Sales Phone: #Model.DealershipInfo.SalesPHNumber</label>
</div>
</div>
}
if(Model.Contacts.Count() >0)
{
<table>
<tr>
<td>Title</td>
<td>Contact Name</td>
<td>Digital Contact</td>
<td>Phone Type</td>
<td>Phone Number</td>
</tr>
#foreach (var contact in Model.Contacts)
{
<tr>
<td>
#contact.Title
</td>
<td>
#contact.ContactName
</td>
<td>
#contact.DigitalContact
</td>
<td>
#contact.PhoneType
</td>
<td>
#contact.PhoneNumber
</td>
</tr>
}
</table>
}
if(#Model.DirectContacts.Count >0)
{
for (var i = 0; i < Model.DirectContacts.Count(); i++)
{
<label>#Model.DirectContacts[i].Department: #Model.DirectContacts[i].Href</label><br />
}
}
}
Can I even do incremental asynchronous development on this project, or does the whole thing need to be converted to one that uses Tasks?
Edit: Solved
I wound up going with a hybrid approach that combined the 2 answers that I received. From what Stephen wrote, I abandoned the BackgroundWorker control that I was familiar with, and like Ronnie pointed out, I needed to make the call to GetCurrentDealers asynchronous as well.
public static async Task<CurrentDealerModel> GetCurrentDealersAsync(CurrentDealerModel model)
{
bool includeDealerDetails = true;
if (model == null)
{
includeDealerDetails = false;
model = new CurrentDealerModel();
}
using (var conn = new OleDbConnection(m_conStr))
{
conn.Open();
var tasks = new List<Task>();
var taskGetDealerInfo = GetDealerInfoAsync(conn);
var taskGetDealershipInfo = GetDealershipInfoAsync(conn);
var taskGetContacts = GetContactsAsync(conn);
var taskGetDirectContacts = GetDirectContactsAsync(conn);
tasks.Add(taskGetDealerInfo);
if (includeDealerDetails)
{
tasks.Add(taskGetDealershipInfo);
tasks.Add(taskGetContacts);
tasks.Add(taskGetDirectContacts);
}
while (0 < tasks.Count)
{
var done = await Task.WhenAny(tasks);
if (!done.IsFaulted)
{
if (done == taskGetDealerInfo)
{
model.Dealers = taskGetDealerInfo.Result;
}
else if (done == taskGetDealershipInfo)
{
model.AllDealerships = taskGetDealershipInfo.Result;
}
else if (done == taskGetContacts)
{
model.Contacts = taskGetContacts.Result;
}
else if (done == taskGetDirectContacts)
{
model.DirectContacts = taskGetDirectContacts.Result;
}
} else
{
throw done.Exception.Flatten();
}
tasks.Remove(done);
}
}
return model;
}
Each of the tasks are small and basic, like this one:
private static async Task<DealerSelectInfoList> GetDealerInfoAsync(OleDbConnection openConnection)
{
var result = new DealerSelectInfoList();
var table = new DataTable();
using (var cmd = new OleDbCommand("", openConnection))
{
cmd.CommandText = $#"
SELECT DISTINCT(DADNAM) AS DADNAM, DADLRN, DASRGN
FROM {m_library}.DLRINVAPF
WHERE DASRGN IN(SELECT RGNAMEM FROM {m_library}.REGIONPF)
ORDER BY DADNAM ASC for read only with UR";
var reader = await cmd.ExecuteReaderAsync();
table.Load(reader);
}
foreach (DataRow row in table.Rows)
{
result.Add(new DealerSelectInfo()
{
DealerName = $"{row["DADNAM"]}".Trim(),
DealerNumber = $"{row["DADLRN"]}".Trim(),
});
}
return result;
}
Your method GetCurrentDealers() isn't async so you can't call it with await.
Edit
Controller
public async Task<IActionResult> DealerSelect()
{
var model = await RetailActivityModelData.GetCurrentDealersAsync(null, ViewBag.Library);
return View(model);
}
GetCurrentDealersAsync()
public static async Task<DealerModel> GetCurrentDealersAsync(DealerModel model, string library)
{
m_library = library;
if (model == null)
{
model = new AdminCurrentDealerModel();
}
using (var bg = new BackgroundWorker())
{
var mre = new ManualResetEvent(false);
bg.DoWork += delegate (object s, DoWorkEventArgs e)
{
// fill 3 DataTables with calls to database
};
bg.ProgressChanged += delegate (object s, ProgressChangedEventArgs e)
{
// add data from DataTables to model
};
bg.RunWorkerCompleted += delegate (object s, RunWorkerCompletedEventArgs e)
{
mre.Set();
};
bg.RunWorkerAsync();
if (bg.IsBusy)
{
mre.WaitOne();
}
}
return model;
}
ASP.NET MVC 5 with .NET Framework v4.5.2
Please see my article Introduction to Async on ASP.NET. In particular, ensure you have httpRuntime.targetFramework set correctly to avoid the server-side "quirks mode".
make a basic BackgroundWorker call
You should never use BackgroundWorker on ASP.NET. And almost never use Task.Run (or StartNew). You cannot "make something asynchronous" this way.
The exception you encounter is a "safety net" exception.
I don't really understand how to turn that into an asynchronous call
Don't start at the controllers. That's actually the opposite of the easiest way to do it. Instead, start with the lowest-level code you have that performs any I/O. E.g., database queries or calls to external APIs. One at a time, make those asynchronous (using await to call asynchronous APIs), and then allow the async/await usage to grow out from there. Eventually, your controller methods will become async as the last step of this process.
Can I even do incremental asynchronous development on this project, or does the whole thing need to be converted to one that uses Tasks?
As you're converting to async, you may find it useful to (temporarily) have two copies of the code: one async (used by code that is already converted) and one sync (used by unconverted code). This is fine if you're doing an conversion-to-async that won't be interrupted by other feature work. If you need longer-term support for both async and sync code, then you can use the boolean argument hack described in my Brownfield Async article (or if you want a more modern/complex solution, use generic code generation as described on my blog).
I want to get the profile information from steam. so first I've fixed that I can login through steam, I used this tutorial: http://www.oauthforaspnet.com/providers/steam/
But now I want to get the steam profile id from the user that logged in so I can use the JSON from the steam API to get the information from the user.
https://steamcommunity.com/profiles/(this id)
I hope someone can help me, I've searched for hours now, and don't have any result.
var options = new SteamAuthenticationOptions {
ApplicationKey = "Your API Key",
Provider = new OpenIDAuthenticationProvider // Steam is based on OpenID
{
OnAuthenticated = async context =>
{
// Retrieve the user's identity with info like username and steam id in Claims property
var identity = context.Identity;
}
}}; app.UseSteamAuthentication(options);
A while ago we discovered the answer:
1.) insert your key from the tutorial here:
var options = new SteamAuthenticationOptions
{
ApplicationKey = "Your API Key",
Provider = new OpenIDAuthenticationProvider // Steam is based on OpenID
{
OnAuthenticated = async context =>
{
// Retrieve the user's identity with info like username and steam id in Claims property
var identity = context.Identity;
}
}
};
app.UseSteamAuthentication(options);
2.) We discovered that steam is saving a users steam id in the database table called: 'AspNetUserLogins', the providerkey inside that table is an url made out of more pieces. For example:
http://steamcommunity.com/openid/id/here-users-steamid
We only need the users steamid, so we going to split that in step 3.
3.) Make a controller, for example: SteamController. Here we going to add a public string:
public string GetSteamID()
{
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new Steam.Models.ApplicationDbContext()));
var CurrentUser = manager.FindById(User.Identity.GetUserId());
if (User.Identity.Name != "")
{
string url = CurrentUser.Logins.First().ProviderKey;
ViewBag.steamid = url.Split('/')[5]; //here we going to split the providerkey so we get the right part
}
else
{
ViewBag.steamid = "";
}
return ViewBag.steamid;
}
) now we can add some stuff, lets say we going to add profile information. Go to your SteamController and add:
[HttpGet]
public ContentResult GetProfile()
{
string url = string.Format("http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=addyourkeyhere&steamids={0}", this.GetSteamID());
string result = null;
using (var client = new WebClient())
{
result = client.DownloadString(url);
}
return Content(result, "application/json");
}
notice that you have to add your steam key from step 1 in the url.
) make a script called: profile.js. Here we going to add our profile information.
function profilepic() {
$.ajax({
url: 'http://localhost:3365/steam/GetProfile',
dataType: 'json',
success: function (data) {
$.each(data.response.players, function (key, value) {
if ($('.profile')) {
$('.profile').append("<img src='" + value.avatar + "'> <span>" + value.personaname + "</span>")
}
if ($('.profile1')) {
$('.profile1').append("<img src='" + value.avatarfull + "'>")
}
if ($('.username')) {
$('.username').append(value.personaname)
}
console.log(value)
});
}, error: function (httpReq, status, exception) {
console.log(status + " " + exception);
}
});
}
6.) Now we have to do the final step, create a view with the classes, for example:
<div class="userprofile">
<span class="profile1"></span>
<div class="userdescription">
<h2 class="username"></h2>
</div>
</div>
) I hope this will help some people, for more questions, feel free to ask!
I have a working submit button, so when I make changes to my form and click submit the form updates to the changes...
HOWEVER. In the database it shows the new data. But when I RedirectToAction to the last page it doesn't show the updated data.
This is my code (Controller):
[HttpPost]
public ActionResult Save(M2Portal.Areas.Admin.Models.Users.Roles roleForm)
{
try
{
if (ModelState.IsValid)
{
var role = Srvctx.Roles.FirstOrDefault(w => w.RoleID == roleForm.RoleId);
role.RoleDescription = roleForm.RoleDescription;
Srvctx.SubmitChanges();
return RedirectToAction("RoleManagement");
}
return RedirectToAction("RoleManagement");
}
catch
{
return RedirectToAction("RoleManagement");
}
}
so when it hits:
return RedirectToAction("RoleManagement");
it just goes to the page but doesnt refresh the data. but when i look at my database its been changed there.
Am new to ASP.NET MVC and have no idea where to start looking...
Any ideas?
role mangagement:
public ActionResult RoleManagement(string userName)
{
return View("RoleManagement", new UserForm(userName));
}
Rolemangement cshtml:
#foreach (M2DAL.M2Service.Role r in Model.AllRoleIDs)
{
<tr>
<td>#r.RoleName</td> //This is not refreshing, on debug shows the number, but browser shows old number)
<td>#r.RoleID</td>
<td>#Html.ActionLink("Edit Roles", "EditRole", "Users", new { roleId = r.RoleID }, new { #class = "action" }) </td>
</tr>
}
You didn't post the code for RoleManagement action. I don't know where are you storing your model, but are you sure, that this action is reading the DB again ?
There should be something like (just an example)
[HttpGet]
public ActionResult RoleManagement()
{
var roles = Srvctx.Roles.Take(1000);
return roles;
}
I am using MVC 5 / WebApi 2 and AngularJs. I want to display the Logged in username in my view. I know how to display that information using razor but how can I do it with Angular? So basically I need to do this with Angular.
<span >Logged In As: #Html.ActionLink(User.Identity.GetUserName(), "Manage", "Account", routeValues: null, htmlAttributes: new { title = "Manage", #style = "color:white;float:right" })</span>
apiUserController
public class apiUserController : ApiController
{
// GET api/<controller>
public List<ApplicationUser> Get()
{
using (var context = new ApplicationDbContext())
{
List<ApplicationUser> users = new List<ApplicationUser>();
users = context.ApplicationUsers
.ToList();
return users;
}
}
}
Updated
public IHttpActionResult Get()
{
using (var context = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
var user = context.FindById(User.Identity.GetUserId());
var loggedInUser = user.UserName;
return Ok(loggedInUser);
}
}
you'll need to create a service that returns your user information
angular.module('app').factory('Authentication', function ($resource) {
var resource = $resource('/user', {}, {
query: {
method: 'GET',
cache: true
}
});
return resource.get().$promise;
});
* note that you'll need to create and endpoint that will send you the user data as json using web api
once you got it done you'll be able to use it in any controller (let's assume you have a homecontroller, it could be a headercontroller or any other)
angular.module('app').controller('HomeController', ['$scope', 'Authentication', function ($scope, Authentication) {
$scope.authentication = Authentication;
}]);
then use it in your view like:
<span >Logged In As: {{authentication.user.username}} </span>
EDIT:
your api controller as you suggested could be like
public HttpResponseMessage Get()
{
var userId = getCurrentUserId(); //something like that
using (var context = new ApplicationDbContext())
{
ApplicationUser user = new ApplicationUser();
user = context.ApplicationUsers.SingleOrDefault(x=>x.id==userId);
return user;
}
}
try to read http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization
for routing try to read this article (I guess you are using web api 2)
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
If you want to cheat a little, you can do this in <head> in your _Layout:
<script type="text/javascript">
(function(myApp) {
myApp.username = "#User.Identity.GetUserName()";
//optional
myApp.otherStuff = "#moreMvcStuff";
})(window.myApp = window.myApp || {});
</script>
Then start your angular app like this:
(function (myApp) {
"use strict";
//var app = set up your angular app
app.run(["$rootScope",
function ($rootScope) {
$rootScope.appSettings = {
username: myApp.username
};
}
]);
})(window.myApp = window.myApp || {});
What you are doing is creating a single value on the window called myApp (or name it whatever you like) and passing it into your IIFE. This gives you access to it inside your angular script, bot only in that on block. So if you want it to stick around, you need to put it in a service or your rootScope.
In the app.run block, you can stick it in your rootScope or wherever you want it.
Now in your views you can display it with {{appSettings.username}}.
I call this "cheating" because it's specifically for MVC or webforms and it's not the "angular way". If you ever migrated to a fully agnostic html/js client (no asp.net mvc) and web APIs, you'd need to do what is in the currently-accepted answer.
When I try and upload a file using ASP.NET MVC it works fine in FF && Chrome, but in IE and Opera a dialog pops-up which asks me to either download, save or cancel.
Choosing either of the options, prevents the fileupload from working. How can I get round this problem?
public class ImportModel
{
[Required]
[FileExtensions("csv", ErrorMessage = "Please upload a valid .csv file")]
public HttpPostedFileBase File { get; set; }
}
[HttpPost]
public virtual ActionResult StartImport(ImportModel model = null)
{
if (model != null)
{
var status = _importService.StartImport(model);
return Json(status, JsonRequestBehavior.AllowGet);
}
return null;
}
View code below (summarised):
<div id="dlgImport" class="hidden">
#using (Html.BeginForm(MVC.Import.StartImport(), FormMethod.Post, new { #class = "smallForm", id = "frmImport", enctype = "multipart/form-data" }))
{
<div class="fields-inline">
<div class="editor-label">
#Html.Label("File")
</div>
<div class="editor-field">
#Html.TextBoxFor(x => x.File, new { #class="input-file", type = "file" })
#Html.ValidationMessageFor(x => x.File)
</div>
</div>
}
</div>
</div>
$(function() {
$("#frmImport").ajaxForm({
success: function (responseHtml) {
// response is wrapped in pre tags by the browser - the ajax upload is carried out using an iframe
var response = $.parseJSON($(responseHtml).text());
}
});
});
var vm = {
startImport: function () {
if ($("#frmImport").valid()) {
$("#frmImport").submit();
}
}
}
Now that you have posted your code it looks like that you are using the jquery form plugin. As explained in the documentation this plugin supports file uploads using AJAX but you cannot return JSON from your server side script:
Since it is not possible to upload files using the browser's
XMLHttpRequest object, the Form Plugin uses a hidden iframe element to
help with the task. This is a common technique, but it has inherent
limitations. The iframe element is used as the target of the form's
submit operation which means that the server response is written to
the iframe. This is fine if the response type is HTML or XML, but
doesn't work as well if the response type is script or JSON, both of
which often contain characters that need to be repesented using entity
references when found in HTML markup.
To account for the challenges of script and JSON responses, the Form
Plugin allows these responses to be embedded in a textarea element and
it is recommended that you do so for these response types when used in
conjuction with file uploads. Please note, however, that if there is
no file input in the form then the request uses normal XHR to submit
the form (not an iframe). This puts the burden on your server code to
know when to use a textarea and when not to.
So basically your upload controller action should respond with:
<textarea>{"foo":"bar"}</textarea>
instead of:
{"foo":"bar"}
Also you should not use the application/json response content type in this case.
You could write a custom action result to achieve that:
public class FileJsonResult : JsonResult
{
public FileJsonResult(object data)
: base()
{
Data = data;
JsonRequestBehavior = JsonRequestBehavior.AllowGet;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Write("<textarea>");
base.ExecuteResult(context);
context.HttpContext.Response.Write("</textarea>");
context.HttpContext.Response.ContentType = "text/html";
}
}
and then:
[HttpPost]
public virtual ActionResult StartImport(ImportModel model = null)
{
if (model != null)
{
var status = _importService.StartImport(model);
return new FileJsonResult(status);
}
new FileJsonResult(new { status = false, errorMessage = "The model was null" });
}
Now you might also need to adapt your success handler to strip the <textarea> tags:
$('#frmImport').ajaxForm({
success: function (responseHtml) {
var responseHtml = responseHtml
.replace(/\<textarea\>/i, '')
.replace(/\<\/textarea\>/i, '');
var response = $.parseJSON(responseHtml);
// do something with the response
}
});
I had the same problem with IE8 and this answer helped me a lot. But I needed to make some changes that worked very well in IE8, Chrome and Firefox.
Follow changes below:
success: function (responseText) {
try{
var response = $.parseJSON(responseText);
//code ...
}
catch(ex) {
//error code
}
}
[HttpPost]
public JsonResult Upload(HttpPostedFileBase file) {
//code
return Json(new { Success = "true", Message = "Done!" }, "text/html");
}