My user is Authenticated but has extra information stored in a FormsAuthenticationTicket then a cookie.
// Custom UserData will contain the following | seperated values:
// [ FullName | ContactNumber1 | ContactNumber2 ]
string userData = fullName + "|" + contactNumber1 + "|" + contactNumber2
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
userName,
DateTime.Now,
DateTime.Now.AddMinutes(20),
false,
userData,
FormsAuthentication.FormsCookiePath);
Create Cookie etc etc
All this is working fine.
Im using mvc 3 and Razor
I have a foo.Layout.cshtml that displays the Fullname / ContactNumber etc etc at the top of the page.
I could include code at the top of the foo.Layout.cshtml to extract an display these values:
#{
FormsAuthenticationTicket ticket = ( (FormsIdentity)User.Identity ).Ticket;
// Custom UserData will contain the following | seperated values:
// [ FullName | ContactNumber1 | ContactNumber2 ]
string[] userData = ticket.UserData.Split(new char[] { '|' });
string fullName = userData[0];
string contactNumber1 = userData[1];
string contactNumber2 = userData[2];
}
<div>#fullName</div>
<div>#contactNumber1</div>
<div>#contactNumber2</div>
This means I dont have to include this code in my controllers but to be honest I dont like it and in my actual app (different values) I need the values in the majority of my views including the layout.
So I have debated including the code inside my controller:
Create a viewmodel:
public class UserViewModel {
public string FullName { get; set; }
public string ContactNumber1 { get; set; }
public string ContactNumber2 { get; set; }
}
Create Controller
public class FooController : Controller {
private readonly _userViewModel = null;
public FooController( ) {
// NOTE this would actually be behind an interface in some type of
// repository / service pattern and injected but for the sake
// of easy conversation ive put it here.
FormsAuthenticationTicket ticket = ( (FormsIdentity)User.Identity ).Ticket;
// Custom UserData will contain the following | seperated values:
// [ FullName | ContactNumber1 | ContactNumber2 ]
string[] userData = ticket.UserData.Split(new char[] { '|' });
_userViewModel = new UserViewModel();
userViewModel.FullName = userData[0];
userViewModel.ContactNumber1 = userData[1];
userViewModel.ContactNumber2 = userData[2];
}
public ActionResult Index() {
(HOWEVER_SET_ON_LAYOUT_PAGE) = userViewModel.FullName;
// If not posting ViewModel
(HOWEVER_SET_ON_THIS_PAGE) = userViewModel.FullName;
// If posting ViewModel
return View(_userViewModel);
}
Maybe some of this could be put inside the controller factory.
Im just looking for discussion or ideas of how other people are approaching this quite common setup.
My actual task is to display the Users Full Name at the top of each page via the Layout and display the Full Name (and other data) at the bottom of various forms that are being filled in then submitted. After postback the Full Name will then be re read from cookie and sent to be stored with the other form fields. Does that make sense and is it really that time :-/
I've addressed this exact thing a couple times in recent projects.
Even better than putting this in the controller would be to create an Authentication service that wraps your FormsAuthentication-specific code, then inject that service via IoC where ever you may need it. Pretty much any IoC container should be able to also specify that your Authentication service could be cached per-http request, so you can take advantage of that to help conserve resources / enhance performance.
Now if you need to display this user info in lots of places, I would encourage you to create a separate UserController and do a RenderAction call to it wherever the User info is needed in your views, instead of making your user view model a part of a bunch of other view models (unless it's actually needed by the other models/views to satisfy some business conditions, etc.). This way you can have a standard, "canonical" view of the user info, as a component, and you'll get lots of reuse out of it.
Related
In a public facing web app multi tenant app, I'm using ASP Identity 2.x.
I let 3rd party's e.g. non-profits/comps self-register, create and populate their own roles and users. The code below is fine, if its an intranet scenarios where everyone belongs to the same company, it does not for work multiple non-profits
The non-profits registrants (understandably so) are naming roles with the same names, i.e. Managers and Employees etc. which are common/same across the database.
How can I extend ASP Identity to separate the roles per organization in a multi-tenant fashion, can you help me understand the design and how extend this, do I need a sub-role? i.e.
what do I do to ensure roles are scoped per organization at the database, so that different org's can have the same role names?
and, what do I do at the middle tier, i.e. usermanager, role manager objects level (middle tier)
//Roles/Create
[HttpPost]
public ActionResult Create(FormCollection form)
{
try
{
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{ \\ Question - can I add another level here like company??
Name = form["RoleName"]
});
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully";
return RedirectToAction("RoleCreated");
}
catch
{
return View();
}
}`
Question- When adding a role, how do I separate the role to know which Role is from Which company when I add to the user?
public ActionResult RoleAddToUser(string UserName, string RoleName)
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var account = new AccountController();
account.UserManager.AddToRole(user.Id, RoleName);
ViewBag.ResultMessage = "Role created successfully !";
// prepopulat roles for the view dropdown
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View("ManageUserRoles");
}
how do I get the list of Roles and Users for that non-profit?
public ActionResult ManageUserRoles()
{
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View();
}`
I'm guessing you do have some sort of TenantId or CompanyId that is an identifier to the tenant you are working with.
IdentityRole and IdentityUser are framework objects that is recommended to inherit to add your own properties. You should do just that:
public class MyApplicationRole : IdentityRole
{
public int CompanyId { get; set; }
}
public class MyApplicationuser : IdentityUser
{
public int CompanyId { get; set; }
}
You can also add reference to a company object here and link it as a foreign key. This might make your life easier retrieving company objects related to the user.
Based on the objects above I'll try answering your questions:
When roles are created you can append a CompanyId to the name as a prefix. Something like <CompanyId>#CustomerServiceRole. This will avoid name clashes. At the same time add CompanyId to the MyApplicationRole class. Prefixed identifier will avoid clashes, identifier in the object will make the roles discoverable by the company. Alternatively you can implement RoleValidator and do the validation based on unique role name within a company. But then you will have to change the code that creates user identity when users are logged in, as role ids are not stored into the cookie.
I think this is self explanatory from the previous answer:
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{
CompanyId = companyId, // TODO Get the company Id
Name = companyId.ToString() + "#" + form["RoleName"]
});
Self explanatory, see code snippets above.
Depending how you link your roles to companies the query will be different. Basically you'll need to do a join between companies and matching roles based on CompanyId and then filter companies by the non-profit flag.
I am using MVC, Entity Framework, Durandal and Breeze JS. I've got a user which looks like such (simplified):
public class User : EntityBase<Guid>, IAggregateRoot
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey("UserImage")]
public virtual Guid? ImageId { get; set; }
public virtual UserImage UserImage { get; set; }
}
The UserImage class looks like such. I know I should limit the size of the Image. (Maybe this is the issue?):
public class UserImage
{
public Guid Id { get; set; }
[MaxLength]
public byte[] Image { get; set; }
}
I've got an api function on the server to get the current user:
public IQueryable<User> GetCurrentUser()
{
IPrincipal principal = HttpContext.Current.User;
var users = _uow.Users.FindBy(u => u.UserName.Equals(principal.Identity.Name));
if (!users.Any())
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized));
}
return users;
}
And two calls on the client which get the current user. The first is in the shell:
function loadCurrentUser() {
return uow.CurrentUser.all().then(function (newUser) {
log('Welcome to the Site ' + newUser[0].FullName() + '!', newUser[0], true);
config.CurrentUser(newUser[0]);
return true;
});
}
The second is in a ManageUser viewmodel:
function activate() {
return uow.CurrentUser.all(['UserImage']).then(function (user) {
self.CurrentUser(user[0]);
return $.when(init()).then(boot());
}).fail(function() {
return router.activate('accounts/login');
});
}
Now I can load an Image into the ManageUser page and save and in fiddler it shows that the ImageId and Image are being sent across to the server. Then I checked the BeforeSaveEntity intercept and shows two entities being saved.
Updated User with ImageId set
New UserImage
The data is also visible in the database. Now when I refresh the Manage User page I can see the two GetCurrentUser calls in fiddler.
From the shell call I can see that the User is being returned and an ImageId is set but no UserImage was sent over because didn't expand the query.
From the Manage User call I see the User is returned but only the ImageId is sent over and the Image object was OMITTED from the JSON.
Has anyone come across this issue with images? All my other expands appear to be working correctly. Does anyone have any examples on using breeze to save just the filepath to the image and possibly using windows azure for media storage?
I know this probably won't answer your question but I would propose not sending the byte array to the client and rather have an Image Handler on the server side that takes an ImageId as a parameter and then return the image with the relevant Content Type set. An example of this can be found here.
By using this approach you could reference your image from HTML using an img tag with the source set to the Image Hander with the relevant ImageId.
An example using knockout for data binding would be:
<a data-bind="attr: {href: '/Image/' + User.ImageId()}"></a>
This approach enables you to easily add caching on both the server and client which will improve performance. It also removed the need to convert the byte array to an image on the client side, which may or may not be a pain.
Edit:
When saving the managed user, post the Image to an Upload action on the ImageHandler (have a look at this article). This action must return the new Id of the image. After you've received the new Id, update the User.ImageId on client side and call SaveChanges on breeze.
I am trying to build a service in Orchard that allows me to create content through a custom form on a page. The service and the content type definitions look fine to me, but somehow, eventhough I don't get any errors or other signs in the Orchard log files, creating new content using the IContentManager does nothing for me.
Parts involved
The controller accepting the form values
[HttpPost]
public ActionResult Create(CreateSopViewModel viewModel)
{
if(!ModelState.IsValid)
{
var shape = _shape.CreateContent();
shape.Header = _shape.Parts_Title(Title: "New item");
// Add the original fields to the shape.
shape.Title = viewModel.Title;
shape.Description = viewModel.Description;
shape.InitialComments = viewModel.InitialComments;
return new ShapeResult(this, shape);
}
// Store the new procedure in the database
_service.CreateContentItem(
viewModel.Title,viewModel.Description,viewModel.InitialComments);
// Redirect the user back to the homepage.
return Redirect("~/");
}
The service that contains the CreateContentItem method:
public void CreateContentItem(string title, string description, string initialComments)
{
// Initialize a new content item based on the SOP type
var customPart = _services.ContentManager.New<MyCustomPart>("CustomContentType");
customPart.Description = description;
customPart.Identifier = BuildIdentifier(title);
customPart.ContentItem.As<TitlePart>().Title = title;
_services.ContentManager.Create(customPart.ContentItem);
}
The content part + record
public class MyCustomPart: ContentPart<MyCustomPartRecord>
{
[Required]
public string Identifier
{
get { return Record.Identifier; }
set { Record.Identifier = value; }
}
[Required]
public string Description
{
get { return Record.Description; }
set { Record.Description = value; }
}
}
public class MyCustomPartRecord: ContentPartRecord
{
public virtual string Identifier { get; set; }
public virtual string Description { get; set; }
}
The migration
SchemaBuilder.CreateTable(typeof(MyCustomPartRecord).Name, table => table
.ContentPartRecord()
.Column<string>("Description")
.Column<string>("Identifier"));
ContentDefinitionManager.AlterPartDefinition("StandardOperationalProcedurePart", builder => builder
.Attachable(true));
ContentDefinitionManager.AlterTypeDefinition("CustomContentType", builder => builder
.DisplayedAs("Custom Content Type")
.WithPart("TitlePart")
.WithPart("MyCustomPart")
.Creatable(true));
Question
Again, I don't get any errors, not in the log and not in Visual Studio. However, my new content item doesn't get created or at least, I can't see it in the admin section of the site under Content.
What is going on and how can I debug this behavior?
I had a similar problem, which was solved when I used the overloaded Create method taking a VersionOptions enum value:
content.Create(customPart.ContentItem, VersionOptions.Published);
This should work even if the content item is not creatable, as mine isn't.
I had a similar issue. In my case the item did appear eventually, but not right away.
The solution for me was to do:
_contentManager.Flush();
I was having this issue, in my case it was that I actually had an error in the database (trying to put 100+ characters into a field that would only hold 100!).
I found the error I was getting (null id in Orchard.Indexing.Models.IndexingTaskRecord entry (don't flush the Session after an exception occurs) ), actually masked the issue. I had to go hunt in the logs to find the real problem.
So anyway, my advice is if you see that contentmanager.create seems to be doing nothing, and any errors don't seem to help, check the logs carefully. They can be found in the logs sub-folder of the appdata folder in the main Orchard.Web project. Because as I've found in the last 48 hours, often the answer is there.
Just trying to do some quick and dirty testing. I am passing fake data through my controller to a view just to see how the UI looks.
In my controller which I set up just to "test" this I have, for example:
MyViewModel = new MyViewModel
{
MyModel= new Models.MyModel
{
FirstName = "Homer", //This works
SomeDecimal = 10000, //This works
SomeRadioButton = Models.MyModel.Enum.Selection, //This works
SomeCheckBox = Models.MyModel.OtherEnum.OtherSelection, //This doesn't
}
}
I am getting the Cannot implicitly convert type ... to 'System.Collections.Generic.List<string>' error.
My radio buttons and check boxes share similar convention for using enums, but the checkbox uses public List<string> SomeCheckBox { get; set; } whereas radio buttons use public Enum? SomeRadioButton { get; set; }
Please note, I am not using a testing framework. I am just trying to figure out quickly how to pass some fake data to see how the UI is shaping up. Can anyone share a sample of how to accomplish what I want (to pass a checkbox value so my UI can show data that would have been selected by a user)?
The other problem I forsee is in passing more than one selection from the checkbox, but once I get the code down I think I should be able to figure that out.
Thanks.
Your error message makes total sense. Try this
var testViewModel = new TestViewModel
{
SomeCheckBox = new List<string> {TestViewModel.RadioButtonValues.Value1.ToString() }
};
In a ASP.NET MVC 4 app I have a view with a paged list (just a simple table, no telerik grid or anything like that). New values are fetched from the database when the user pages through the list.
On every row in that table there is an edit button, when clicking the button you are presented with an edit view and when you click save in that view you are redirected back to the view with the paged list.
The urls for the list view looks like this
http://localhost/Items/Page/1
http://localhost/Items/Page/2
The route looks like this
routes.MapRoute(
name: "ItemsList",
url :"Items/Page/{page}",
defaults: new { controller = "Items", action = "Index", page = 1 },
constraints: new {page = #"\d+"}
);
My question is this: what is the preferred, most common way to store away the referring url, so when done editing an item, I can redirect the user back to the correct url
http://localhost/Items/Page/2
and not just to
http://localhost/Items
I've tried splitting up
Request.UrlReferrer.PathAndQuery
and storing those values around, and then build the url from those values but I have a feeling there is a much better solution to this problem. Any suggestions?
Update
Right now I'm thinking that I could put the UrlReferrer.PathAndQuery (if there are any values) as a property on the view model for the edit screen and then use that when deciding on where to redirect after a save.
Any thoughts out there on that approach?
Here is my final solution to the problem, it's not super elegant but it works.
I added a property to the View model that could store the url. That value get's stored in a hidden field.
public class SkillEditModel
{
public int Id { get; set; }
public string Name { get; set; }
public string RedirectBackToUrl { get; set; }
}
In the controller Edit(GET) method I store the value with the view model
if (!Request.UrlReferrer == null)
{
model.RedirectBackToUrl = Request.UrlReferrer.PathAndQuery;
}
And finally after saving the changes in Edit (POST) I did this
if (!string.IsNullOrWhiteSpace(model.RedirectBackToUrl))
{
return new RedirectResult(model.RedirectBackToUrl);
}
return RedirectToAction("Index");