I am trying to access a table using its controller from another controller method.
But when the method tries to call the table controller method I get an exception:
Exception=System.NullReferenceException: Object reference not set to an instance of an object. at Microsoft.WindowsAzure.Mobile.Service.TableController.....
I manage to access the table controller method from the web API and execute it successfully.
I tried the same thing with TodoItem given as an example by the initial mobile service.
After several publishes to the server trying to fix the issue the web API stopped working and I get this exception : An exception of type 'Microsoft.WindowsAzure.MobileServices.MobileServiceInvalidOperationException' occurred in mscorlib.dll but was not handled in user code
Additional information: The request could not be completed. (Internal Server Error) I managed to solve it when I reopened a mobile service and database with the exact same code that didn't work.
Any tips ?
Here is my table controller created by the controller wizard:
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.OData;
using Microsoft.WindowsAzure.Mobile.Service;
using FringProjectMobileService.DataObjects;
using FringProjectMobileService.Models;
namespace FringProjectMobileService.Controllers
{
public class StorageItemController : TableController<StorageItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
FringProjectMobileServiceContext context = new FringProjectMobileServiceContext();
DomainManager = new EntityDomainManager<StorageItem>(context, Request, Services);
}
// GET tables/StorageItem
public IQueryable<StorageItem> GetAllStorageItem()
{
return Query();
}
// GET tables/StorageItem/xxxxxxxxxx
public SingleResult<StorageItem> GetStorageItem(string id)
{
return Lookup(id);
}
// PATCH tables/StorageItem/xxxxxxxx
public Task<StorageItem> PatchStorageItem(string id, Delta<StorageItem> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/StorageItem
public async Task<IHttpActionResult> PostStorageItem(StorageItem item)
{
StorageItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/StorageItem/xxxxxxxxxx
public Task DeleteStorageItem(string id)
{
return DeleteAsync(id);
}
}
}
Below the other controller code trying to access the method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Microsoft.WindowsAzure.Mobile.Service;
namespace FringProjectMobileService.Controllers
{
public class ArduinoController : ApiController
{
public ApiServices Services { get; set; }
// GET api/Arduino
public string Get()
{
Services.Log.Info("Hello from custom controller!");
return "Hello";
}
public async void PostProcessTag(String id)
{
Microsoft.WindowsAzure.MobileServices.MobileServiceClient client = new Microsoft.WindowsAzure.MobileServices.MobileServiceClient("http://some-service.azure-mobile.net", "XXXXXXXXXXXXXXX");
Microsoft.WindowsAzure.MobileServices.IMobileServiceTable<DataObjects.StorageItem> storage_item_table = client.GetTable<DataObjects.StorageItem>();
await storage_item_table.ToEnumerableAsync();
}
}
}
I also tried a different implementation for the method :
public void PostProcessTag(String id)
{
StorageItemController table_controller = new StorageItemController();
IQueryable<DataObjects.StorageItem> item = table_controller.GetAllStorageItem();
}
The service context:
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using Microsoft.WindowsAzure.Mobile.Service;
using Microsoft.WindowsAzure.Mobile.Service.Tables;
namespace FringProjectMobileService.Models
{
public class FringProjectMobileServiceContext : DbContext
{
// You can add custom code to this file. Changes will not be overwritten.
//
// If you want Entity Framework to alter your database
// automatically whenever you change your model schema, please use data migrations.
// For more information refer to the documentation:
// http://msdn.microsoft.com/en-us/data/jj591621.aspx
//
// To enable Entity Framework migrations in the cloud, please ensure that the
// service name, set by the 'MS_MobileServiceName' AppSettings in the local
// Web.config, is the same as the service name when hosted in Azure.
private const string connectionStringName = "Name=MS_TableConnectionString";
public FringProjectMobileServiceContext() : base(connectionStringName)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
string schema = ServiceSettingsDictionary.GetSchemaName();
if (!string.IsNullOrEmpty(schema))
{
modelBuilder.HasDefaultSchema(schema);
}
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
public System.Data.Entity.DbSet<FringProjectMobileService.DataObjects.StorageItem> StorageItems { get; set; }
}
}
Related
I have created HttpGet following ASP.NET Core identity but I'm having problem to get data based on role. In my program I have three roles user, manager, admin. And in GET method only login user should be able to list data where admin can list all the company data but user and manager can see/list only those company data where they are associate.
Here is my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Web.Data;
using Web.Features.Roles;
using Web.Features.Company;
using Web.Features.Shared;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Web.Controllers
{
[ApiController]
[Route("api/company")]
public class CompanyController : ControllerBase
{
private readonly DataContext dataContext;
public CompanyController(DataContext dataContext)
{
this.dataContext = dataContext;
}
private static Expression<Func<Company, CompanyDto>> MapToDto()
{
return x => new CompanyDto
{
Name = x.Name,
Active = x.Active,
CompanyPopulation = x.CompanyPopulation,
Id = x.Id
};
}
[HttpGet]
[Authorize]
public IEnumerable<CompanyDto> GetAll()
{
var result = dataContext
.Set<Company>()
.Select(MapToDto()).ToList();
if (User.IsInRole("admin")) //I tried to check using IsInRole method but it doesn't work
{
return result;
}
return null;
}
}
}
CompanyDto
public class CompanyDto
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public int CompanyPopulation { get; set; }
}
I tried to check using IsInRole method but it doesn't work
Firstly, you can try to get list of role names of current user using following code snippet, and check if you assigned role(s) to current user.
var user = await _userManager.GetUserAsync(User);
var roles = await _userManager.GetRolesAsync(user);
If you do not assign user with specified role, please refer to the following code snippet to add the specified user to the named role.
var user = await _userManager.GetUserAsync(User);
await _userManager.AddToRoleAsync(user, "role_name_here");
Test Result
Have spent way too much time trying to figure this out. Thanks for any help. .Net Core 3.1 trying to register a service in Startup.cs
Error CS0311: The type 'Apex.UI.MVC.ProjectService' cannot be used as type parameter 'TImplementation' in the generic type or method ServiceCollectionServiceExtensions.AddScoped<TService, TImplementation>(IServiceCollection). There is no implicit reference conversion from 'Apex.UI.MVC.ProjectService' to 'Apex.EF.Data.IProjects'. (CS0311) (Apex.UI.MVC)
services.AddScoped<IProjects, ProjectService>();
using System;
using Apex.EF.Data;
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
using System.Linq;
using Apex.UI.MVC.Models.Projects;
namespace Apex.UI.MVC.Controllers
{
public class ProjectController : Controller
{
private IProjects _projects;
public ProjectController(IProjects projects)
{
_projects = projects;
}
public IActionResult Index()
{
var projectModels = _projects.GetAll();
var listingResult = projectModels
.Select(result => new ProjectIndexListingModel
{
Id = result.Id,
ProjectName = result.ProjectName,
ProjectImage = result.ProjectImage
});
var model = new ProjectIndexModel()
{
Project = listingResult
};
return View(model);
}
}
}
using System;
using System.Collections.Generic;
using Apex.EF.Data;
using Apex.EF.Data.Models;
namespace Apex.EF.Data
{
public interface IProjects
{
IEnumerable<Project> GetAll();
Project GetById(int id);
void Add(Project newProject);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Apex.EF.Data;
using Apex.EF.Data.Models;
using Microsoft.EntityFrameworkCore;
namespace ApexServices
{
public class ProjectService : IProjects
{
private ApexContext _context;
public ProjectService(ApexContext context)
{
_context = context;
}
public void Add(Project newProject)
{
_context.Add(newProject);
_context.SaveChanges();
}
public IEnumerable<Project> GetAll()
{
return _context.Projects
.Include(project => project.Status.IsInShop == true);
}
public Project GetById(int id)
{
return _context.Projects
.Include(project => project.Status.IsInShop==true)
.FirstOrDefault(project => project.Id == id);
}
}
}
The namespaces shown in the exception are different to the example code shown. There are probably conflicting types in the project (not shown).
If that is really the case, then include the full namespace when registering the type with the container to avoid conflicts.
Based on the shown code, that would be
services.AddScoped<IProjects, ApexServices.ProjectService>();
I am using a third party authentication procedure to authorize my pages in Nancy. I have tried to do it in MVC and it is successfull but I cannot reproduce the same results in Nancy.
Here is what I am doing:
MVC-Startup:
using Microsoft.Owin;
using Owin;
using Passport.Auth;
[assembly: OwinStartup(typeof(TestAuthorization.Startup))]
namespace TestAuthorization
{
public partial class Startup:StartupBase
{
public void Configuration(IAppBuilder app)
{
base.Configuration(app);
}
public override string IdentityServerUri
{
get { return "https://test.ThirdParty.URL/identity"; }
}
public override string RedirectUri
{
get { return "https://localhost:4443"; }
}
public override string ApplicationClientId
{
get { return "local.fox.company"; }
}
}
}
Nancy-Startup:
using Microsoft.Owin;
using Owin;
using Passport.Auth;
using Nancy.Owin;
[assembly: OwinStartupAttribute(typeof(AgreementManagementTool.Startup))]
namespace AgreementManagementTool
{
public class Startup: StartupBase
{
public void Configuration(IAppBuilder app)
{
app.UseNancy();
base.Configuration(app);
}
public override string IdentityServerUri
{
get { return "https://test.ThirdParty.URL/identity"; }
}
public override string RedirectUri
{
get { return "https://localhost:4443"; }
}
public override string ApplicationClientId
{
get { return "local.fox.company"; }
}
}
}
Now here is my program.cs for Nancy only:
class Program
{
static void Main(string[] args)
{
var uri = "https://+:4443"; //"https://localhost:4443";
Console.WriteLine("Starting Nancy on " + uri);
using (WebApp.Start<Startup>(uri))
{
Console.WriteLine("\n\nServer listening at {0}. Press enter to stop", uri);
Console.ReadLine();
return;
}
}
}
Now all I have to do is write [Authorize] on top of my Nancy module and it should work just like MVC.
MVC-Controller:
using System.Web.Mvc;
namespace TestAuthorization.Controllers
{
[Authorize]
public class HomeController : Controller
{
public ActionResult Index()
{
//this.RequiresMSOwinAuthentication();
return View();
}
}
}
Nancy-Module:
using Nancy;
using AgreementManagementTool.Views.Home.Model;
using System.Web.Mvc;
namespace AgreementManagementTool.Modules
{
[Authorize]
public class HomeModule : NancyModule
{
public HomeModule()
: base("/home")
{
Get["/"] = parameters =>
{
//this.RequiresMSOwinAuthentication(); // Not working
//this.RequiresAuthentication(); // Not working
HomeModel result = new HomeModel();
result.ResultImport = "I am testing AUthentication";
return View["index", result];
};
}
}
}
When I browse to the page after running the MVC application run successfully and authorization work successfull, but nancy doesnot show anything.
I have tried to use this.RequiresAuthentication(); but it throws an exception:
Nancy-Exception
Just to mention that I have no idea how the third party authentication process works, I just have to use it.
In MVC I have recieved the sample and it is working fine, why is it not working the same in nancy.
Nancy does not use [Authorize] attribute.
Have a look sample using Identity server for Nancy specific implementation.
If you are just using Nancy, try this to replace both your startups (using Owin.Security.Cookies):
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
namespace TestOwin
{
public class Startup
{
// OWIN Configuration goes here
public static void Configuration(IAppBuilder app)
{
var cookieOptions = new CookieAuthenticationOptions() { AuthenticationMode = AuthenticationMode.Active };
var nancyCfg = new NancyOptions();
nancyCfg.Bootstrapper = new NBootstrapper();
app.UseStaticFiles();
app.UseCookieAuthentication(cookieOptions);
app.UseNancy(nancyCfg);
}
}
}
And in your NancyBootstrapper (Here is where you can redirect users to a login page):
public class NBootstrapper : DefaultNancyBootstrapper
{
//Nancy Bootstrapper overrides go here
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
// We don't call "base" here to prevent auto-discovery of
// types/dependencies
}
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
base.ConfigureRequestContainer(container, context);
// Here we register our user mapper as a per-request singleton.
// As this is now per-request we could inject a request scoped
// database "context" or other request scoped services.
container.Register<IUserMapper, UserValidator>();
}
protected override void RequestStartup(TinyIoCContainer requestContainer, IPipelines pipelines, NancyContext context)
{
// At request startup we modify the request pipelines to
// include forms authentication - passing in our now request
// scoped user name mapper.
//
// The pipelines passed in here are specific to this request,
// so we can add/remove/update items in them as we please.
var formsAuthConfiguration =
new FormsAuthenticationConfiguration()
{
RedirectUrl = "~/login",
UserMapper = requestContainer.Resolve<IUserMapper>(),
};
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
}
}
And then you can use:
this.RequiresMSOwinAuthentication();
on your modules.
Hope this helps! My problem is trying to also get the same authentication to work inside Nancy and outside too, with SignalR...
Shibboleth is a SSO Authentication that is added to IIS as a "plugin".
After a user has done a Login there are Headers showing the Shibboleth Session:
ShibSessionID
ShibIdentityProvider
eppn
affiliation
entitlement
unscopedaffiliation
...more
So i can extract username and roles from the Headers.
so far so fine.
Question:
How can I implement a handler that does read the headers and set the status that a user is authorized?
Idea is to use the
[Authorize]
Attribute and the Method
Roles.IsUserInRole.
All from the Headers, no Database, no User Management.
Update
Implementation According to the Answer from #Pharylon
In this Update there is nothing new, just a help for the copy&past friends.
Of course you have to adjust the properties and Header fieldnames according to your Shibboleth Setup.
File: ShibbolethPrincipal.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal; //GenericPrincipal
namespace Shibboleth
{
public class ShibbolethPrincipal : GenericPrincipal
{
public string username
{
get { return this.Identity.Name.Replace("#ksz.ch", ""); }
}
public string firstname
{
get { return HttpContext.Current.Request.Headers["givenName"]; }
}
public string lastname
{
get { return HttpContext.Current.Request.Headers["surname"]; }
}
public string phone
{
get { return HttpContext.Current.Request.Headers["telephoneNumber"]; }
}
public string mobile
{
get { return HttpContext.Current.Request.Headers["mobile"]; }
}
public string entitlement
{
get { return HttpContext.Current.Request.Headers["eduzgEntitlement"]; }
}
public string homeOrganization
{
get { return HttpContext.Current.Request.Headers["homeOrganization"]; }
}
public DateTime birthday
{
get
{
DateTime dtHappy = DateTime.MinValue;
try
{
dtHappy = DateTime.Parse(HttpContext.Current.Request.Headers["dateOfBirth"]);
}
finally
{
}
return dtHappy;
}
set {}
}
public ShibbolethPrincipal()
: base(new GenericIdentity(GetUserIdentityFromHeaders()), GetRolesFromHeader())
{
}
public static string GetUserIdentityFromHeaders()
{
//return HttpContext.Current.Request.Headers["eppn"];
return HttpContext.Current.Request.Headers["principalName"];
}
public static string[] GetRolesFromHeader()
{
string[] roles = null;
//string rolesheader = HttpContext.Current.Request.Headers["affiliation"];
string rolesheader = HttpContext.Current.Request.Headers["eduzgEntitlement"];
if (rolesheader != null)
{
roles = rolesheader.Split(';');
}
return roles;
}
}
}
File: ShibbolethController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Shibboleth
{
public class ShibbolethController : Controller
{
protected new ShibbolethPrincipal User
{
get
{
return (base.User as ShibbolethPrincipal) ?? null; //CustomPrincipal.GetUnauthorizedPrincipal();
}
}
}
}
File: Global.asax
void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var ctx = HttpContext.Current;
var principal = new ShibbolethPrincipal();
HttpContext.Current.User = principal;
}
Using examples:
namespace itservices.Controllers
{
[Authorize] //examples : [Authorize(Roles="Administrators")], [Authorize(Users="Alice,Bob")]
public class PasswordMailController : ShibbolethController
{
if(User.IsInRole("staff"))
{
You'll want to create a method in Global.asax.cs that has the following signature
protected void Application_PostAuthenticateRequest()
{
//Your code here.
}
This will be called automatically before almost anything else is done (MVC will call this method if it exists, you don't have to "turn it on" anywhere), and this is where you need to set the Principal. For instance, let's assume you have a header called RolesHeader that has a comma separated value of roles and another header called UserId that has (duh) the user ID.
Your code, without any error handling, might look something like:
protected void Application_PostAuthenticateRequest()
{
var rolesheader = Context.Request.Headers["RolesHeader"];
var userId = Context.Request.Headers["UserId"];
var roles = rolesheader.Split(',');
var principal = new GenericPrincipal(new GenericIdentity(userId), roles);
Context.User = principal;
}
It's the Principal/Identity that the [Authorize] attribute uses, so setting it here at the beginning of the request lifecycle means the [Authorize] attribute will work correctly.
The rest of this is optional, but I recommend it:
I like to create my own custom classes that implement IPrincipal and IIdentity instead of using the GenericPrincipal and GenericIdentity, so I can stuff more user information in it. My custom Principal and Identity objects then have much more rich information, such as branch numbers or email addresses or whatever.
Then, I create a Controller called BaseController that has the following
protected new CustomPrincipal User
{
get
{
return (base.User as CustomPrincipal) ?? CustomPrincipal.GetUnauthorizedPrincipal();
}
}
This allows me to access all my rich, custom Principal data instead of just what's defined in IPrincipal. All of my real controllers then inherit from BaseController instead of directly from Controller.
Obviously, when using a custom Principal like this, in the Application_PostAuthenticateRequest() method, you'd set the Context.User to be your CustomPrincipal instead of a GenericPrincipal.
How to asynchronously save an entity to Windows Azure Table Service?
The code below works synchronously but raises an exception when trying to save asynchronously.
This statement:
context.BeginSaveChangesWithRetries(SaveChangesOptions.Batch,
(asyncResult => context.EndSaveChanges(asyncResult)), null);
Results in System.ArgumentException: "The current object did not originate the async result. Parameter name: asyncResult".
Additionally, what's the correct pattern for creating the service context when saving asynchronously? Should I create a separate context for each write operation? Is it too expensive (e.g. requiring a call over the network)?
TableStorageWriter.cs:
using System;
using System.Data.Services.Client;
using System.Diagnostics;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
namespace WorkerRole1
{
public class TableStorageWriter
{
private const string _tableName = "StorageTest";
private readonly CloudStorageAccount _storageAccount;
private CloudTableClient _tableClient;
public TableStorageWriter()
{
_storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
_tableClient = _storageAccount.CreateCloudTableClient();
_tableClient.CreateTableIfNotExist(_tableName);
}
public void Write(string message)
{
try
{
DateTime now = DateTime.UtcNow;
var entity = new StorageTestEntity
{
Message = message,
PartitionKey = string.Format("{0:yyyy-MM-dd}", now),
RowKey = string.Format("{0:HH:mm:ss.fff}-{1}", now, Guid.NewGuid())
};
// Should I get this context before each write? It is efficient?
TableServiceContext context = _tableClient.GetDataServiceContext();
context.AddObject(_tableName, entity);
// This statement works but it's synchronous
context.SaveChangesWithRetries();
// This attempt at saving asynchronously results in System.ArgumentException:
// The current object did not originate the async result. Parameter name: asyncResult
// context.BeginSaveChangesWithRetries(SaveChangesOptions.Batch,
// (asyncResult => context.EndSaveChanges(asyncResult)), null);
}
catch (StorageClientException e)
{
Debug.WriteLine("Error: {0}", e.Message);
Debug.WriteLine("Extended error info: {0} : {1}",
e.ExtendedErrorInformation.ErrorCode,
e.ExtendedErrorInformation.ErrorMessage);
}
}
}
internal class StorageTestEntity : TableServiceEntity
{
public string Message { get; set; }
}
}
Called from WorkerRole.cs:
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure.ServiceRuntime;
using log4net;
namespace WorkerRole1
{
public class WorkerRole : RoleEntryPoint
{
public override void Run()
{
var storageWriter = new TableStorageWriter();
while (true)
{
Thread.Sleep(10000);
storageWriter.Write("Working...");
}
}
public override bool OnStart()
{
ServicePointManager.DefaultConnectionLimit = 12;
return base.OnStart();
}
}
}
Examples using Windows Azure SDK for .NET 1.8.
You should call EndSaveChangesWithRetries instead of EndSaveChanges, as otherwise the IAsyncResult object returned by BeginSaveChangesWithRetries cannot be used by EndSaveChanges. So, could you please try changing your End method call as below?
context.BeginSaveChangesWithRetries(SaveChangesOptions.Batch,
(asyncResult => context.EndSaveChangesWithRetries(asyncResult)),
null);
And for your other question, I would recommend creating a new TableServiceContext for each call, as DataServiceContext is not stateless (MSDN) and the way you implemented TableStorageWriter.Write with the asynchronous call might allow concurrent operations. Actually, in Storage Client Library 2.0, we explicitly prevented concurrent operations that uses a single TableServiceContext object. Moreover, creating a TableServiceContext does not result in a request to Azure Storage.