Is there a way to do "AND" in Net SQL AzMan instead of "OR"? - azman

All of the settings in Net SQL AzMan seem to be "OR" based.
For example:
If you add 3 (Authorized) Application Groups to an operation, a user needs to be in the first OR the second OR the third to have permissions for the operation.
I am looking for a way to say the user needs to be in (the first AND the second) OR (the first AND the third).
Is there a way to do that?
Reason Why:
We have users that snowball permissions as they move from department to department. I want to setup one role per Active Directory Departement ("the first" in my example above). If I can get the above logic working then when the user changes departments they will lose the permissions from their former department (even if their boss is lazy and does not get AzMan updated).
If I can't get this working in AzMan, then I can have my apps do it. But it would be so much easier at the AzMan level.

You could do this with a BizRule on the operation. The code for it is a bit overkill, but this should work with minimal modifications.
using System;
using System.Security.Principal;
using System.IO;
using System.Data;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;
using NetSqlAzMan;
using NetSqlAzMan.Interfaces;
using System.Security.Principal;
using System.Reflection;
namespace APPLICATION.BizRules
{
public sealed class BizRule : IAzManBizRule
{
public BizRule()
{ }
public bool Execute(Hashtable contextParameters, IAzManSid identity, IAzManItem ownerItem, ref AuthorizationType authorizationType)
{
string sqlConnectionString = "data source=DATABASE_FQN;initial catalog=DATABASE;Integrated Security=false;User Id=USER_NAME;Password=PASSWORD";
IAzManStorage storage = new SqlAzManStorage(sqlConnectionString);
try
{
bool authorized = false;
if (identity.StringValue.StartsWith("S"))
{
//this is a little over kill but there is no way to reference standard .net libraries in NetSqlAzMan
Assembly asm = Assembly.Load(#"System.DirectoryServices.AccountManagement, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089");
System.Type userPrincipalType = asm.GetType("System.DirectoryServices.AccountManagement.UserPrincipal");
System.Type principalContextType = asm.GetType("System.DirectoryServices.AccountManagement.PrincipalContext");
System.Type contextTypeType = asm.GetType("System.DirectoryServices.AccountManagement.ContextType");
System.Type identityTypeType = asm.GetType("System.DirectoryServices.AccountManagement.IdentityType");
Object principalContext = Activator.CreateInstance(principalContextType, new object[] { Enum.ToObject(contextTypeType, 1), "DENALLIX" });
MethodInfo methodInfo = userPrincipalType.GetMethod("FindByIdentity", new Type[] { principalContextType, identityTypeType, typeof(string) });
Object userPrincipal = methodInfo.Invoke(null, new object[] { principalContext, Enum.ToObject(identityTypeType, 4), identity.StringValue });
string userPrincipalName = userPrincipal.GetType().GetProperty("UserPrincipalName").GetValue(userPrincipal, null).ToString();
WindowsIdentity user = new WindowsIdentity(userPrincipalName);
authorized = (checkRoleAuthorization(storage, "GROUP1", user) && checkRoleAuthorization(storage, "GROUP2", user)) || checkRoleAuthorization(storage, "GROUP3", user);
}
else
{
AzManUser user = new AzManUser(identity);
authorized = (checkRoleAuthorization(storage, "GROUP1", user) && checkRoleAuthorization(storage, "GROUP2", user)) || checkRoleAuthorization(storage, "GROUP3", user);
}
return authorized;
}
catch (SqlAzManException ex)
{
return false;
}
}
private bool checkRoleAuthorization(IAzManStorage storage, string roleName, object user)
{
AuthorizationType auth = AuthorizationType.Deny;
if (user is WindowsIdentity)
{
auth = storage.CheckAccess("MY STORE", "MY APPLICATION", roleName, (WindowsIdentity)user, DateTime.Now, true);
}
else
{
auth = storage.CheckAccess("MY STORE", "MY APPLICATION", roleName, (IAzManDBUser)user, DateTime.Now, true);
}
return auth == AuthorizationType.Allow || auth == AuthorizationType.AllowWithDelegation;
}
}
public partial class AzManUser : IAzManDBUser
{
private Dictionary<string, object> _customColumns = new Dictionary<string, object>();
private IAzManSid _sid;
private string _username;
public AzManUser(string username, string sid)
{
_username = username;
_sid = new NetSqlAzMan.SqlAzManSID(sid);
}
public AzManUser(string sid)
{
_username = string.Empty;
_sid = new NetSqlAzMan.SqlAzManSID(sid);
}
public AzManUser(IAzManSid sid)
{
_username = string.Empty;
_sid = sid;
}
public Dictionary<string, object> CustomColumns
{
get { return _customColumns; }
}
public IAzManSid CustomSid
{
get
{
return _sid;
}
}
public string UserName
{
get { return _username; }
}
}
}

Related

ASP .NET Core MVC upload text files to an existing db with logged in user foreign key

Now I'm working on my Cloud Storage Project and the main idea of it is to use authorization system provided by Identity Framework and let users upload and download files to an existing table (AspNetFiles - created by me). Moreover, it's important to save all uploaded files to folder in project directory (~/wwwroot/Files/). Now I'm on the way to build upload files system, so:
I made new table AspNetFiles in the direction of other AspNet... tables provided by Identity and Upload-Database NuGet func after creating Migration;
Created new "WorkSpaceController" in "~/Controllers/" for managing files (upload, sort in grid and download) for every logged in user;
Created functions for FileManager view (this is the page for displaying upload, grid and delete files experience) + some other functions for saving files in wwwroot, getting logged in user "Id" and etc.
My dbo.AspNetFiles has the next columns:
FileID (PK, int, not null) with identity (1,1) parameter
FileName (varchar(60), not null)
FileData (varbinary(max), not null) - for store uploaded file data in table
FileExtension (varchar(15), not null)
FileDate (varchar(20), not null)
Id (FK, nvarchar(450), not null) as the primary key of logged in user from dbo.AspNetUsers
After debugging application I get some errors:
InvalidCastException: Object must implement IConvertible. System.Convert.ChangeType(object value, Type conversionType, IFormatProvider provider)
InvalidCastException: Failed to convert parameter value from a FormFile to a Byte[]. Microsoft.Data.SqlClient.SqlParameter.CoerceValue(object value, MetaType destinationType, out bool coercedToDataFeed, out bool typeChanged, bool allowStreaming)
So yeah I know that I use a IFormFile type for "FileData: from "FileDataModel" but it's for saving file locally in project folder as I mentioned previously (~/wwwroot/Files/).
I'm a new user in ASP.NET Core, so I tried many ways from YouTube and articles of how to save files locally and in table of SQL database, but I didn't find any way to do it both and save files with existing Identity Framework with connection to logged in user by foreing key "Id" in table for upload files and download to pc.
Hope u can help me with it. Don't gudge too much :D
This is the code:
FileDataModel (in ~/Models/)
namespace TextCloud.Models
{
public class FileDataModel
{
public string FileName { get; set; }
public IFormFile FileData { get; set; }
public string FileExtension { get; set; }
public string FileDate { get; set; }
public string Id { get; set; }
}
}
WorkSpaceController (in ~/Controllers/)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Configuration;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols;
using System.Data;
using System.Data.SqlClient;
using TextCloud.Models;
using Microsoft.Data.SqlClient;
using Microsoft.AspNetCore.Hosting;
using TextCloud.Areas.Identity.Data;
namespace TextCloud.Controllers
{
public class WorkSpaceController : Controller
{
private readonly IWebHostEnvironment webHostEnvironment;
private readonly UserManager<TextCloudUser> _userManager;
public WorkSpaceController(IWebHostEnvironment webHostEnvironment, UserManager<TextCloudUser> userManager)
{
this.webHostEnvironment = webHostEnvironment;
_userManager = userManager;
}
public IConfigurationRoot GetConnection()
{
var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appSettings.json").Build();
return builder;
}
public IActionResult FileManager()
{
return View();
}
[HttpPost]
public IActionResult FileManager(FileDataModel model)
{
if (ModelState.IsValid)
{
string uniqueFileName = null;
if (model.FileData != null)
{
DateTime FileDate = DateTime.Now;
model.FileDate = FileDate.ToString("dd/MM/yyyy");
model.Id = _userManager.GetUserId(HttpContext.User);
string uploadsFolder = Path.Combine(webHostEnvironment.WebRootPath, "Storage");
uniqueFileName = Guid.NewGuid().ToString() + "_" + model.FileData.FileName;
string filePath = Path.Combine(uploadsFolder, uniqueFileName);
model.FileExtension = Path.GetExtension(model.FileName);
model.FileName = model.FileData.FileName;
if (model.FileDate != null)
{
string connctn = "Server=DESKTOP-LRLFA5K\\SQLEXPRESS;Database=TextCloud;Trusted_Connection=True;MultipleActiveResultSets=true";
SqlConnection con = new SqlConnection(connctn);
con.Open();
string commnd = "insert into AspNetFiles(FileName, FileData, FileExtension, FileDate, Id) values (#FileName, #FileData, #FileExtension, #FileDate, #Id)";
SqlCommand com = new SqlCommand(commnd, con);
com.Parameters.Add("#FileName", SqlDbType.VarChar).Value = model.FileName;
com.Parameters.Add("#FileData", SqlDbType.VarBinary).Value = model.FileData;
com.Parameters.Add("#FileExtension", SqlDbType.VarChar).Value = model.FileExtension;
com.Parameters.Add("#FileDate", SqlDbType.VarChar).Value = model.FileDate;
com.Parameters.Add("#Id", SqlDbType.NVarChar).Value = model.Id;
com.ExecuteScalar();
con.Close();
model.FileData.CopyTo(new FileStream(filePath, FileMode.Create));
}
}
}
return View();
}
}
}
According to your description, I found you directly pass the iformfile to the FileData. You should read the byte array from the iformfile, then store the byte arrary into the database.
More details, you could refer to below codes:
[HttpPost]
public IActionResult FileManager(FileDataModel model)
{
if (ModelState.IsValid)
{
string uniqueFileName = null;
if (model.FileData != null)
{
DateTime FileDate = DateTime.Now;
model.FileDate = FileDate.ToString("dd/MM/yyyy");
model.Id = _userManager.GetUserId(HttpContext.User);
string uploadsFolder = Path.Combine(webHostEnvironment.WebRootPath, "Storage");
uniqueFileName = Guid.NewGuid().ToString() + "_" + model.FileData.FileName;
string filePath = Path.Combine(uploadsFolder, uniqueFileName);
model.FileExtension = Path.GetExtension(model.FileName);
model.FileName = model.FileData.FileName;
if (model.FileDate != null)
{
using (MemoryStream stream = new MemoryStream())
{
model.FileData.OpenReadStream().CopyTo(stream);
string connctn = #"Server=DESKTOP-LRLFA5K\\SQLEXPRESS;Database=TextCloud;Trusted_Connection=True;MultipleActiveResultSets=true";
SqlConnection con = new SqlConnection(connctn);
con.Open();
string commnd = "insert into AspNetFiles(FileName, FileData, FileExtension, FileDate, Id) values (#FileName, #FileData, #FileExtension, #FileDate, #Id)";
SqlCommand com = new SqlCommand(commnd, con);
com.Parameters.Add("#FileName", SqlDbType.VarChar).Value = model.FileName;
com.Parameters.Add("#FileData", SqlDbType.VarBinary).Value = stream.ToArray();
com.Parameters.Add("#FileExtension", SqlDbType.VarChar).Value = model.FileExtension;
com.Parameters.Add("#FileDate", SqlDbType.VarChar).Value = model.FileDate;
com.Parameters.Add("#Id", SqlDbType.NVarChar).Value = model.Id;
com.ExecuteScalar();
con.Close();
stream.CopyTo(new FileStream(filePath, FileMode.Create));
}
}
}
}
return View();
}

Calling Dynamics Web API with Entity metadata early binding

I would like to consume my organizations dynamics oData endpoint but with early bound classes. However, there are a lot of early bound tools out there and I wanted to know which one provides the best developer experience/least resistance?
For example, there is this one:
https://github.com/daryllabar/DLaB.Xrm.XrmToolBoxTools
https://github.com/yagasoft/DynamicsCrm-CodeGenerator
and so on. Is there a developer preference/method out there?
Early bound classes are for use with the Organization Service which is a SOAP service. The normal way to generate those classes is using CrmSvcUtil.
OData can be used in Organization Data Service or Web API, but those don't have Early Bound classes.
Further reading: Introducing the Microsoft Dynamics 365 web services
It's not impossible to use with standard SOAP Early bound class. We just have to be creative. If we work just with basic attributes (fields, not relationships, ecc) it seems possible. For example. for create and update, OData will not accept the entire early bounded class, just pass the attibutes:
class Program
{
static void Main(string[] args)
{
string token = System.Threading.Tasks.Task.Run(() => GetToken()).Result;
CRMWebAPI dynamicsWebAPI = new CRMWebAPI("https:/ORG.api.crm4.dynamics.com/api/data/v9.1/",
token);
CRMGetListOptions listOptions = new CRMGetListOptions
{
Select = new string[] { "EntitySetName" },
Filter = "LogicalName eq 'contact'"
};
dynamic entityDefinitions = dynamicsWebAPI.GetList<ExpandoObject>("EntityDefinitions", listOptions).Result;
Contact contact = new Contact
{
FirstName = "Felipe",
LastName = "Test",
MobilePhone = "38421254"
};
dynamic ret = System.Threading.Tasks.Task.Run(async () => await dynamicsWebAPI.Create(entityDefinitions.List[0].EntitySetName, KeyPairValueToObject(contact.Attributes))).Result;
}
public static async Task<string> GetToken()
{
string api = "https://ORG.api.crm4.dynamics.com/";
ClientCredential credential = new ClientCredential("CLIENT_ID", "CLIENT_SECRET");
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/commom/oauth2/authorize");
return authenticationContext.AcquireTokenAsync(api, credential).Result.AccessToken;
}
public static object KeyPairValueToObject(AttributeCollection keyValuePairs)
{
dynamic expando = new ExpandoObject();
var obj = expando as IDictionary<string, object>;
foreach (var keyValuePair in keyValuePairs)
obj.Add(keyValuePair.Key, keyValuePair.Value);
return obj;
}
}
It's a simple approach and I didn't went further.
Maybe we have to serealize other objects as OptionSets, DateTime (pass just the string) and EntityReferences but this simple test worked fine to me. I'm using Xrm.Tools.WebAPI and Microsoft.IdentityModel.Clients.ActiveDirectory. Maybe it's a way.
[Edit]
And so I decided to go and created a not well tested method to cast the attributes. Problems: We have to follow OData statments to use the API. To update/create an entity reference we can use this to reference https://www.inogic.com/blog/2016/02/set-values-of-all-data-types-using-web-api-in-dynamics-crm/
So
//To EntityReference
entityToUpdateOrCreate["FIELD_SCHEMA_NAME#odata.bind"] = "/ENTITY_SET_NAME(GUID)";
So, it's the Schema name, not field name. If you use CamelCase when set you fields name you'll have a problem where. We can resolve that with a (to that cute) code
public static object EntityToObject<T>(T entity) where T : Entity
{
dynamic expando = new ExpandoObject();
var obj = expando as IDictionary<string, object>;
foreach (var keyValuePair in entity.Attributes)
{
obj.Add(GetFieldName(entity, keyValuePair), CastEntityAttibutesValueOnDynamicObject(keyValuePair.Value));
}
return obj;
}
public static object CastEntityAttibutesValueOnDynamicObject(object attributeValue)
{
if (attributeValue.GetType().Name == "EntityReference")
{
CRMGetListOptions listOptions = new CRMGetListOptions
{
Select = new string[] { "EntitySetName" },
Filter = $"LogicalName eq '{((EntityReference)attributeValue).LogicalName}'"
};
dynamic entitySetName = dynamicsWebAPI.GetList<ExpandoObject>("EntityDefinitions", listOptions).Result.List[0];
return $"/{entitySetName.EntitySetName}({((EntityReference)attributeValue).Id})";
}
else if (attributeValue.GetType().Name == "OptionSetValue")
{
return ((OptionSetValue)attributeValue).Value;
}
else if (attributeValue.GetType().Name == "DateTime")
{
return ((DateTime)attributeValue).ToString("yyyy-MM-dd");
}
else if (attributeValue.GetType().Name == "Money")
{
return ((Money)attributeValue).Value;
}
else if (attributeValue.GetType().Name == "AliasedValue")
{
return CastEntityAttibutesValueOnDynamicObject(((AliasedValue)attributeValue).Value);
}
else
{
return attributeValue;
}
}
public static string GetFieldName<T>(T entity, KeyValuePair<string, object> keyValuePair) where T : Entity
{
switch (keyValuePair.Value.GetType().Name)
{
case "EntityReference":
var entityNameList = System.Threading.Tasks.Task.Run(async () => await dynamicsWebAPI.GetEntityDisplayNameList()).Result;
var firstEntity = entityNameList.Where(x => x.LogicalName == entity.LogicalName).FirstOrDefault();
var attrNameList = System.Threading.Tasks.Task.Run(async () => await dynamicsWebAPI.GetAttributeDisplayNameList(firstEntity.MetadataId)).Result;
return attrNameList.Where(x => x.LogicalName == keyValuePair.Key).Single().SchemaName + "#odata.bind";
case "ActivityParty":
throw new NotImplementedException(); //TODO
default:
return keyValuePair.Key;
}
}
Please, note that this approach do not seems fast or good in anyway. It's better if you have all this values as static so we can save some fetches
[Edit 2]
I just found on XRMToolBox a plugin called "Early bound generator for Web API" and it seems to be the best option. Maybe you should give it a try if you're still curious about that. I guess its the best approach.
The final code is this:
static void Main(string[] args)
{
string token = Task.Run(() => GetToken()).Result;
dynamicsWebAPI = new CRMWebAPI("https://ORG.api.crm4.dynamics.com/api/data/v9.1/",
token);
Contact contact = new Contact
{
FirstName = "Felipe",
LastName = "Test",
MobilePhone = "38421254",
new_Salutation = new EntityReference(new_salutation.EntitySetName, new Guid("{BFA27540-7BB9-E611-80EE-FC15B4281C8C}")),
BirthDate = new DateTime(1993, 04, 14),
};
dynamic ret = Task.Run(async () => await dynamicsWebAPI.Create(Contact.EntitySetName, contact.ToExpandoObject())).Result;
Contact createdContact = dynamicsWebAPI.Get<Contact>(Contact.EntitySetName, ret, new CRMGetListOptions
{
Select = new string[] { "*" }
}).Result;
}
and you have to change the ToExpandoObject on Entity.cs class (generated by the plugin)
public ExpandoObject ToExpandoObject()
{
dynamic expando = new ExpandoObject();
var expandoObject = expando as IDictionary<string, object>;
foreach (var attributes in Attributes)
{
if (attributes.Key == GetIdAttribute())
{
continue;
}
var value = attributes.Value;
var key = attributes.Key;
if (value is EntityReference entityReference)
{
value = $"/{entityReference.EntitySetName}({entityReference.EntityId})";
}
else
{
key = key.ToLower();
if (value is DateTime dateTimeValue)
{
var propertyForAttribute = GetPublicInstanceProperties().FirstOrDefault(x =>
x.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase));
if (propertyForAttribute != null)
{
var onlyDateAttr = propertyForAttribute.GetCustomAttribute<OnlyDateAttribute>();
if (onlyDateAttr != null)
{
value = dateTimeValue.ToString(OnlyDateAttribute.Format);
}
}
}
}
expandoObject.Add(key, value);
}
return (ExpandoObject)expandoObject;
}
Links:
https://github.com/davidyack/Xrm.Tools.CRMWebAPI
https://www.xrmtoolbox.com/plugins/crm.webApi.earlyBoundGenerator/
We currently use XrmToolkit which has it's own version of early binding called ProxyClasses but will allow you to generate early binding using the CRM Service Utility (CrmSvcUtil). It does a lot more than just early binding which is why we use it on all of our projects but the early binding features alone would have me sold on it. in order to regenerate an entity definition all you do is right click the cs file in visual studio and select regenerate and it is done in a few seconds.
For my first 3 years of CRM development I used the XrmToolbox "Early Bound Generator" plugin which is really helpful as well.

Forbid users from executing WebApi actions

I have the following WebApi action that deletes an order from the back-end database, only for users that are in the Admin and Order roles. However, if the user is also in the Readonly role the action returns a HTTP 403 Forbidden response.
[Authorize(Roles = "Admin,Order")]
public async Task<IHttpActionResult> Delete(int orderid) {
if(User.IsInRole("Readonly")) { return Forbidden(); }
var order = await _repository.Get(orderid);
if(order != null) {
await _repository.Delete(orderid);
return NoContent();
}
else {
return NotFound();
}
}
What I'd like to know is it possible to prevent actions from being executed if users are in specific roles so that I do not have to put if(User.IsInRole("Readonly")) { return Forbidden(); } at the start of all database update-able action methods, e.g.
[Authorize(Roles = "Admin,Order")]
[NotAuthorized(Roles = "Readonly")]
public async Task<IHttpActionResult> Delete(int orderid) {
var order = await _repository.Get(orderid);
if(order != null) {
await _repository.Delete(orderid);
return NoContent();
}
else {
return NotFound();
}
}
The NotAuthorized action filter will return a HTTP 403 Forbidden response if the user is in the Readonly role.
Is this possible?
This is the code to implement a reverse of the [Authorize()] attribute and forbid users from executing MVC WebApi actions if they are a member of one or more roles.
using System;
using System.Net;
using System.Net.Http;
using System.Security.Principal;
using System.Web.Http;
using System.Web.Http.Controllers;
namespace MyAPI {
[AttributeUsage(AttributeTargets.Method,AllowMultiple = false)]
public class NotAuthorizedAttribute : AuthorizeAttribute {
public override void OnAuthorization(HttpActionContext actionContext) {
IPrincipal user = actionContext.RequestContext.Principal;
if(!user.Identity.IsAuthenticated) {
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
else {
bool userInRole = false;
foreach(var role in Roles.Split(',')) {
if(user.IsInRole(role)) {
userInRole = true;
break;
}
}
if(userInRole) {
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
}
}
To use this filter attribute simply decorate any actions that you don't want users to execute if they're a member of a restricted role, e.g. if the user is part of a read-only role they not permitted to update the database:
[Authorize(Roles = "Admin,Order")]
[NotAuthorized(Roles = "Readonly")]
public async Task<IHttpActionResult> Delete(int orderid) {
var order = await _repository.Get(orderid);
if(order != null) {
await _repository.Delete(orderid);
return NoContent();
}
else {
return NotFound();
}
}

Integrating AspNet.WebApi with AspNet.SignalR

I want to integrate Microsoft.AspNet.SignalR version="2.1.2" with Microsoft.AspNet.WebApi version="5.2.2" so that the API can communicate in REAL-TIME. I found one VERY NICE SAMPLE that implements/works exactly the same way I want, but the sample uses jquery.signalR-0.5.0.js. Some of the earlier implementations have changed, and so far here is what I have done in a failed effort to upgrade the solution to use the latest signalr, asp.net web api and owin.
I left the Hub as it is
using SignalR.Hubs;
namespace NdcDemo.Hubs
{
// This hub has no inbound APIs, since all inbound communication is done
// via the HTTP API. It's here for clients which want to get continuous
// notification of changes to the ToDo database.
[HubName("todo")]
public class ToDoHub : Hub { }
}
I also left the ApiControllerWithHub class as it is
using System;
using System.Web.Http;
using SignalR;
using SignalR.Hubs;
namespace NdcDemo.Controllers
{
public abstract class ApiControllerWithHub<THub> : ApiController
where THub : IHub
{
Lazy<IHubContext> hub = new Lazy<IHubContext>(
() => GlobalHost.ConnectionManager.GetHubContext<THub>()
);
protected IHubContext Hub
{
get { return hub.Value; }
}
}
}
For the ToDoController, I changed the
Hub.Clients.addItem(item), Hub.Clients.updateItem(toUpdate),
Hub.Clients.deleteItem(id)
to
Hub.Clients.All.addItem(item), Hub.Clients.All.updateItem(toUpdate),
Hub.Clients.All.deleteItem(id)
and this is now the full ToDoController class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Web.Http;
using NdcDemo.Hubs;
using NdcDemo.Models;
namespace NdcDemo.Controllers
{
[InvalidModelStateFilter]
public class ToDoController : ApiControllerWithHub<ToDoHub>
{
private static List<ToDoItem> db = new List<ToDoItem>
{
new ToDoItem { ID = 0, Title = "Do a silly demo on-stage at NDC" },
new ToDoItem { ID = 1, Title = "Wash the car" },
new ToDoItem { ID = 2, Title = "Get a haircut", Finished = true }
};
private static int lastId = db.Max(tdi => tdi.ID);
public IEnumerable<ToDoItem> GetToDoItems()
{
lock (db)
return db.ToArray();
}
public ToDoItem GetToDoItem(int id)
{
lock (db)
{
var item = db.SingleOrDefault(i => i.ID == id);
if (item == null)
throw new HttpResponseException(
Request.CreateResponse(HttpStatusCode.NotFound)
);
return item;
}
}
public HttpResponseMessage PostNewToDoItem(ToDoItem item)
{
lock (db)
{
// Add item to the "database"
item.ID = Interlocked.Increment(ref lastId);
db.Add(item);
// Notify the connected clients
Hub.Clients.addItem(item);
// Return the new item, inside a 201 response
var response = Request.CreateResponse(HttpStatusCode.Created, item);
string link = Url.Link("apiRoute", new { controller = "todo", id = item.ID });
response.Headers.Location = new Uri(link);
return response;
}
}
public ToDoItem PutUpdatedToDoItem(int id, ToDoItem item)
{
lock (db)
{
// Find the existing item
var toUpdate = db.SingleOrDefault(i => i.ID == id);
if (toUpdate == null)
throw new HttpResponseException(
Request.CreateResponse(HttpStatusCode.NotFound)
);
// Update the editable fields and save back to the "database"
toUpdate.Title = item.Title;
toUpdate.Finished = item.Finished;
// Notify the connected clients
Hub.Clients.updateItem(toUpdate);
// Return the updated item
return toUpdate;
}
}
public HttpResponseMessage DeleteToDoItem(int id)
{
lock (db)
{
int removeCount = db.RemoveAll(i => i.ID == id);
if (removeCount <= 0)
return Request.CreateResponse(HttpStatusCode.NotFound);
// Notify the connected clients
Hub.Clients.deleteItem(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
}
}
And then I put app.MapSignalR();
But the demo doesn't work...the API doesn't contact clients...Where am I going wrong?
I would still appreciate any more simpler recommendations based on this solution.
Solution from OP.
Answer:
After a a cup of camomile tea, i found out that the clients had to include the KEYWORD CLIENT before the dynamic methods in Todo.js... So, here is what that needs to be modified so that the sample works
hub.client.addItem = function (item) {
alert("i just received something...");
viewModel.add(item.ID, item.Title, item.Finished);
};
hub.client.deleteItem = function (id) {
viewModel.remove(id);
};
hub.client.updateItem = function (item) {
viewModel.update(item.ID, item.Title, item.Finished);
};
And it works!

LINQ to MongoDB: .Any with a Predicate

I have a Collection in MongoDB of S documents. Each S has a collection of UserPermission objects, each of which have a UserId property. I want to select all the S documents that have a UserPermission with a certain UserId:
return collection.Where(s => s.UserPermissions.Any(up => up.UserId == userIdString)).ToList();
I get an error telling me that .Any with a predicate is not supported. The MongoDB docs say: "You can usually rewrite such a query by putting an equivalent where clause before the projection (in which case you can drop the projection)."
What does that mean? Any idea how I would change my query to get around this limitation?
Here's a full example. You can see I've tried 2 different queries and neither is supported:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace MongoSample
{
class Program
{
static void Main(string[] args)
{
App app1 = new App() { Name = "App1", Users = new List<User>()
{
new User() { UserName = "Chris" } }
};
App app2 = new App() { Name = "App2", Users = new List<User>()
{
new User() { UserName = "Chris" },
new User() { UserName = "Carlos" } }
};
MongoServer server = MongoServer.Create();
MongoDatabase database = server.GetDatabase("test");
MongoCollection appCollection = database.GetCollection("app");
appCollection.Insert(app1);
appCollection.Insert(app2);
// Throws "Any with a predicate not supported" error
//var query = appCollection.AsQueryable<App>()
// .Where(a => a.Users.Any(u => u.UserName == "Carlos"));
// Throws "Unsupported Where Clause" error.
var query = appCollection.AsQueryable<App>()
.Where(a => a.Users.Where(u => u.UserName == "Carlos").Any());
foreach (App loadedApp in query)
{
Console.WriteLine(loadedApp.ToJson());
}
Console.ReadLine();
}
}
class App
{
public string Name { get; set; }
public List<User> Users { get; set; }
}
class User
{
public string UserName { get; set; }
}
}
Any() without a predicate is supported, so you can do:
collection.Where(s => s.UserPermissions
.Where(up => up.UserId == userIdString).Any() )
(this is the "equivalent where clause" put before the Any)

Resources