I'm building an history of the action a user called. For this I need all the parameters in the original querystring. This needs to be done in the OnActionExecuted (After the action) because some description information are stored in the filterAction.Result.Model.MyDescription.
On the ActionExecutedContext I do not have access to the ActionParameters and the RoutesValues only contains action, controller, id and culture. It doesn't contains any of the other parameters that was included in the querystring.
How can I preserve the ActionParameters from the OnActionExecuting to the OnActionExecuted?
Controller:
[ReportHistoryLink]
public ActionResult Index(int id, DateTime at, string by)
{
var model = new ReportModel();
model.ReportDescription = "Some logic get the ReportDescription";
return View("Index", model);
}
ActionFilter:
public class ReportHistoryLinkAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var model = (IModelWithReportDescription)((ViewResult)filterContext.Result).Model;
var description = model.ReportDescription;
var boxedId = filterContext.RouteData.Values["id"];
var id = Convert.ToInt32(boxedId);
if (id != 0 && !string.IsNullOrWhiteSpace(targetDescription))
{
var link = new HistoryLink
{
Id = id,
Description = description,
Route = filterContext.RouteData.Values //This doesn't contains the "at and by parameters"
};
}
}
}
You can use
filterContext.HttpContext.Request.QueryString["id"]
Related
Since the two following controller's actions are possible, I am wondering if there is any difference in terms of performance (or best practices) between them.
[HttpGet("example1")]
public ActionResult<User> GetExample1()
{
var user = new User { UserName = "Example 1 user" };
var model = new Model<User>(user);
return Ok(model);
}
[HttpGet("example2")]
public ActionResult<Model<User>> GetExample2()
{
var user = new User { UserName = "Example 2 user" };
var model = new Model<User>(user);
return Ok(model);
}
Please notice that the only difference between the two is the returned type of the generic parameter of ActionResult (User vs Model<User>). Is there any important internal differences when the ActionResult object is formatting out the result?.
User and Model code for this example:
public class User
{
public string UserName { get; set; }
}
public class Model<T>
{
public T Data { get; set; }
public Model(T data)
{
Data = data;
}
}
Is there any way to change the value of specific data on model?
Like for example my model contains
item_id
item_description
quantity
and you have many records on your data model with the use of session.
on input during changing the quantity i want to update the value on of model with session?
is it recommended? or is there any better way than my approach? thank you.
What I was trying to achieve is to modify the data inside the
session. Is modifying data on session also working like a database?
where you can get the id and modify the data that you want?
From your description, I assume you are using Session to the list of records, and now you want to update the session data, right? If that is the case, you could refer to the following sample:
Refer this article to enable the session middleware in the Startup.cs file.
[Note]: Please remember to set the session expired time via the IdleTimeout property. And, since you are using session to store a list of Objects, remember to add the SessionExtensions.
//required using Microsoft.AspNetCore.Http;
//required using System.Text.Json;
public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default : JsonSerializer.Deserialize<T>(value);
}
}
Add an Item View Model:
public class ItemViewModel
{
public int ItemId { get; set; }
public string ItemDescription { get; set; }
public int Quantity { get; set; }
}
Controller: I'm using a repository to set the Initial data. After getting data from session, we could modify the data, and then, call the set method to save the latest data to the session.
private readonly ApplicationDbContext _context;
private readonly IDataRepository _repository;
public TestController(ApplicationDbContext context, IDataRepository repository)
{
_context = context;
_repository = repository;
}
private readonly string SessionKeyName = "itemlist";
public IActionResult ItemIndex()
{
List<ItemViewModel> items = new List<ItemViewModel>();
//check if the session is exist or not.
if (HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName) == default)
{ //get the initial data.
items = _repository.GetItemViewModels();
//set value to the session.
HttpContext.Session.Set<List<ItemViewModel>>(SessionKeyName, items);
}
else
{
items = HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName);
}
return View(items);
}
public IActionResult EditItem(int Id)
{
List<ItemViewModel> items = new List<ItemViewModel>();
//check if the session is exist or not.
if (HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName) == default)
{
items = _repository.GetItemViewModels();
//set value to the session.
HttpContext.Session.Set<List<ItemViewModel>>(SessionKeyName, items);
}
else
{
items = HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName);
}
return View(items.Where(c=>c.ItemId == Id).FirstOrDefault());
}
[HttpPost]
public IActionResult EditItem(ItemViewModel item)
{
List<ItemViewModel> items = new List<ItemViewModel>();
//check if the session is exist or not.
if (HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName) == default)
{
items = _repository.GetItemViewModels();
//set value to the session.
HttpContext.Session.Set<List<ItemViewModel>>(SessionKeyName, items);
}
else
{
items = HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName);
}
//based on the primary key to find the special item,
var i = items.Where(c => c.ItemId == item.ItemId).FirstOrDefault();
//update the quantity.
i.Quantity = item.Quantity;
//Update the session with the latest data.
HttpContext.Session.Set<List<ItemViewModel>>(SessionKeyName, items);
//redirect to the ItemIndex action and reload the Index page.
return RedirectToAction(nameof(ItemIndex));
}
The result like this:
I have a controller that requests a model containing an IFormFile as one of it's properties. For the request description, the Swagger UI (I'm using Swashbuckle and OpenApi 3.0 for .NET Core) lists the type of the file property as type object. Is there some way to make the Swagger UI denote the exact type and it's JSON representation to help the client?
The controller requesting the model looks as follows.
[HttpPost]
[Consumes("multipart/form-data")
public async Task<IActionResult> CreateSomethingAndUploadFile ([FromForm]RequestModel model)
{
// do something
}
And the model is defined as below:
public class AssetCreationModel
{
[Required}
public string Filename { get; set; }
[Required]
public IFormFile File { get; set; }
}
We've been exploring this issue today. If you add the following to your startup it will convert IFormFile to the correct type
services.AddSwaggerGen(c => {
c.SchemaRegistryOptions.CustomTypeMappings.Add(typeof(IFormFile), () => new Schema() { Type = "file", Format = "binary"});
});
Also see the following article on file upload in .net core
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1
This problem was already tackled in the following github issue/thread.
This improvement was already merged into Swashbuckle.AspNetCore master (as per 10/30/2018), but i don't expect that to be available as a package soon.
There are simple solutions if you only have a IFormFile as a parameter.
public async Task UploadFile(IFormFile filePayload){}
For simple case you can take a look at the following answer.
For complicated cases like container cases, you can take a look at the following answer.
internal class FormFileOperationFilter : IOperationFilter
{
private struct ContainerParameterData
{
public readonly ParameterDescriptor Parameter;
public readonly PropertyInfo Property;
public string FullName => $"{Parameter.Name}.{Property.Name}";
public string Name => Property.Name;
public ContainerParameterData(ParameterDescriptor parameter, PropertyInfo property)
{
Parameter = parameter;
Property = property;
}
}
private static readonly ImmutableArray<string> iFormFilePropertyNames =
typeof(IFormFile).GetTypeInfo().DeclaredProperties.Select(p => p.Name).ToImmutableArray();
public void Apply(Operation operation, OperationFilterContext context)
{
var parameters = operation.Parameters;
if (parameters == null)
return;
var #params = context.ApiDescription.ActionDescriptor.Parameters;
if (parameters.Count == #params.Count)
return;
var formFileParams =
(from parameter in #params
where parameter.ParameterType.IsAssignableFrom(typeof(IFormFile))
select parameter).ToArray();
var iFormFileType = typeof(IFormFile).GetTypeInfo();
var containerParams =
#params.Select(p => new KeyValuePair<ParameterDescriptor, PropertyInfo[]>(
p, p.ParameterType.GetProperties()))
.Where(pp => pp.Value.Any(p => iFormFileType.IsAssignableFrom(p.PropertyType)))
.SelectMany(p => p.Value.Select(pp => new ContainerParameterData(p.Key, pp)))
.ToImmutableArray();
if (!(formFileParams.Any() || containerParams.Any()))
return;
var consumes = operation.Consumes;
consumes.Clear();
consumes.Add("application/form-data");
if (!containerParams.Any())
{
var nonIFormFileProperties =
parameters.Where(p =>
!(iFormFilePropertyNames.Contains(p.Name)
&& string.Compare(p.In, "formData", StringComparison.OrdinalIgnoreCase) == 0))
.ToImmutableArray();
parameters.Clear();
foreach (var parameter in nonIFormFileProperties) parameters.Add(parameter);
foreach (var parameter in formFileParams)
{
parameters.Add(new NonBodyParameter
{
Name = parameter.Name,
//Required = , // TODO: find a way to determine
Type = "file"
});
}
}
else
{
var paramsToRemove = new List<IParameter>();
foreach (var parameter in containerParams)
{
var parameterFilter = parameter.Property.Name + ".";
paramsToRemove.AddRange(from p in parameters
where p.Name.StartsWith(parameterFilter)
select p);
}
paramsToRemove.ForEach(x => parameters.Remove(x));
foreach (var parameter in containerParams)
{
if (iFormFileType.IsAssignableFrom(parameter.Property.PropertyType))
{
var originalParameter = parameters.FirstOrDefault(param => param.Name == parameter.Name);
parameters.Remove(originalParameter);
parameters.Add(new NonBodyParameter
{
Name = parameter.Name,
Required = originalParameter.Required,
Type = "file",
In = "formData"
});
}
}
}
}
}
You need to look into how you can add some/an OperationFilter that is suitable for your case.
I have simple model:
public class Post
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { set; get; }
//non-important properties stripped, to focus on problem
public virtual Resource Resource { set; get; }
public virtual ICollection<Tag> Tags { set; get; }
}
public class Resource
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { set; get; }
[Url]
public string Url { set; get; }
}
and DbContext (I use ASP.NET identity in this project, if this is relevant):
public class DbContext : IdentityDbContext<User>
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var postEntity = modelBuilder.Entity<Post>();
postEntity.Reference(p => p.Resource).InverseCollection(); //no navigation property on Resource side
postEntity.Collection(p => p.Tags).InverseReference(tag => tag.Post);
postEntity.ToTable("Posts");
var resourceEntity = modelBuilder.Entity<Resource>();
resourceEntity.ToTable("Resources");
var tagEntity = modelBuilder.Entity<Tag>();
tagEntity.Reference(t => t.Post).InverseCollection(p => p.Tags).Required(false);
tagEntity.ToTable("Tags");
}
}
After migration (SQL Server), database tables looks like expected - Post table has Foreign Key to ResourceId.
Creating Post's works fine, when I attach post.Resource (already created Resource).
My problem occurs when I want to replace post.Resource.
By replace, I mean selecting one of already existing Resources and assigning it to post.
var resource2 = Database.Resources.First(r=>r.Url == "xyz");
I have tried:
post.Resource = resource2; Database.Entry(post).State = EntityState.Modified;
Database.Entry(post).Property(p => p.Resource).CurrentValue = resource2;
post.Resource = null;
With different combinations of them also, but none of them works. After calling await SaveChangesAsync(); and looking up in database - there are no changes. How to perform the replace (update of foreign key) properly?
//Update 14.09.2015
Issue was caused by additional select, performed to update One-To-Many relationship. Full code:
var Database new DbContext();
var resource2 = Database.Resources.First(r=>r.Url == "xyz");
var oldAssignedTags = Database.Posts.Include(p=>p.Tags).FirstOrDefault(p => p.Id == post.Id).Tags;
var tags = newTags as List<Tag> ?? newTags.ToList();
//TagComparer is irrelevant here
var toRemove = oldAssignedTags.Except(tags, TagComparer);
var toAdd = tags.Except(oldAssignedTags, TagComparer);
foreach (var tag in toRemove)
{
Database.Entry(tag).State = EntityState.Deleted; //Database.Tags.Remove(tag);
}
foreach (var tag in toAdd)
{
tag.Post = post;
post.Tags.Add(tag);
}
post.Resource = resource2;
await Database.SaveChangesAsync();
I thought this may have something to do with Eager Loading, however I can't reproduce your issue with or without AspNet.Identity. Running the below code results in the Resource always being updated. Using EntityFramework 7.0.0-beta7.
Code
static void Main(string[] args)
{
Init();
WithEagerLoading();
CleanUp();
Init();
WithoutEagerLoading();
CleanUp();
}
private static void WithoutEagerLoading()
{
var db = new MyContext();
var post = db.Posts.First(); // no eager loading of child
post.Resource = db.Resources.First(p => p.Url == "http://trend.atqu.in");
db.SaveChanges();
Console.WriteLine($"2nd Resource.Id: {new MyContext().Posts.Include(p => p.Resource).First().Resource.Id}");
}
private static void WithEagerLoading()
{
var db = new MyContext();
var post = db.Posts
.Include(p => p.Resource) // eager loading
.First();
post.Resource = db.Resources.First(p => p.Url == "http://trend.atqu.in");
db.SaveChanges();
Console.WriteLine($"2nd Resource.Id: {new MyContext().Posts.Include(p => p.Resource).First().Resource.Id}");
}
private static void CleanUp()
{
var db = new MyContext();
db.Posts.RemoveRange(db.Posts);
db.Resources.RemoveRange(db.Resources);
db.SaveChanges();
}
private static void Init()
{
var db = new MyContext();
var resource = new Resource { Url = "http://atqu.in" };
var resource2 = new Resource { Url = "http://trend.atqu.in" };
var post = new Post { Resource = resource };
db.Add(post);
db.Add(resource);
db.Add(resource2);
db.SaveChanges();
db = new MyContext();
post = db.Posts.Include(p => p.Resource).First();
resource = db.Resources.First(p => p.Url == "http://trend.atqu.in");
Console.WriteLine($"1st Resource.Id: {post.Resource.Id}");
}
Result
1st Resource.Id: 0f4d222b-4184-4a4e-01d1-08d2bc9cea9b
2nd Resource.Id: 00ccae9c-f0da-43e6-01d2-08d2bc9cea9b
1st Resource.Id: 749f08f0-2426-4043-01d3-08d2bc9cea9b
2nd Resource.Id: 2e83b512-e8bd-4583-01d4-08d2bc9cea9b
Edit 16/9
The problem in the edited question's code is because you are instantiating Database after you have retrieved post. post is not attached to that instance of Database, so when you attach the Resource to it and call SaveChangesAsync it does nothing, because post at that time has noting to do with the Database you are saving against. That is why your workaround of selecting post post again (after the instantiation) causes it to be fixed - because then the instance of post is attached. If you don't want to select post again, you should use the same DbContext instance to do the work above that you used to originally retrieve post.
i am writing a unitest for login in my project .
when i call the login function of my controller. Memership.GetUser giving null value for passed User.
below is Test case
[TestMethod]
public void Login()
{
//Arrange
AccountController account = new AccountController(_forgotPasswordTokensRepo, _IMessageTemplateDAO, _IEmailService, _ISettingDAO, _IProfileDAO);
List<AccountBO> TestUsers = new List<AccountBO>();
AccountBO objAccountBO = new AccountBO();
objAccountBO.Email = "uu#yopmail.com";
objAccountBO.Password = "123456789";
TestUsers.Add(objAccountBO);
var result = (JsonResult)account.Login(TestUsers[0]);
var json = new System.Web.Script.Serialization.JavaScriptSerializer();
string data = result.Data.ToString().Split('=')[1].Trim();
bool Processdata = Convert.ToBoolean(data.Replace('}', ' ').Trim());
Assert.AreEqual<bool>(true, Processdata);
}
Controller function is
public JsonResult Login(AccountBO account, string returnUrl = "")
{
bool hasBeenUnlocked = false;
if (ModelState.IsValid)
{
MembershipUser adminUser;
adminUser = Membership.GetUser(account.Email);
------so on
}
}
here adminUser = Membership.GetUser(account.Email) is giving null.
What do you expect GetUser to return? Are you expecting it to query the db/active directory and return a user with full credentials?
Unless I remember wrong, Membership is part of Asp.Net infrastructure so it is not set up in the context of your unit test. The solution is not to set it up. It is to stub out that functionality.
public interface IProvideUsers {
public MembershipUser Get(string email, string password);
}
public class AspNetMembershipProvider : IProvideUsers {
public MembershipUser Get(string email) {
//...
}
}
then in your controller
IProvideUsers users;
....
users.Get(account.Email, account.Password);
where users is injected via the constructor. Then in your tests you create your own implmentation of IProvideUsers and provide that.