Updating Red5 SharedObject closing stream - apache-flex

I'm trying to create a voice conference room with all users can speak and use the mic. But as an Admin, I should have the privilege to mute any user. So, I add to the user an attribute for the mic which will be check in client side and enable/disable user's mic accordingly. The server side code looks like:
String identifier;
String userID;
private int _gId = 1;
private Map<String,Object> newUser;
#Override
public boolean appConnect(IConnection conn, Object[] params) {
identifier = (String)params[1];
userID = (String)params[0];
int _globalUserId = _gId++;
conn.getClient().setAttribute("id", _globalUserId);
newUser = new HashMap<String,Object>();
newUser.put("identifier", (String)params[0]);
newUser.put("mic", 1); //mic value to be checked in client side
return true;
}
#Override
public boolean roomJoin(IClient client, IScope scope) {
ISharedObject so = getSharedObject(scope, "users_so");
so.setAttribute(userID,newUser);
return true;
}
#SuppressWarnings("unchecked")
public void muteUser(String userID){
IScope scope = Red5.getConnectionLocal().getScope();
ISharedObject so = getSharedObject(scope, "users_so");
Map<String,Object> user= new HashMap<String,Object>();
user = (Map<String, Object>) so.getAttribute(userID);
if(user != null){
user.put("mic", 0);
so.beginUpdate();
boolean removed = so.removeAttribute(userID);
boolean updated = so.setAttribute(userID,user);
so.endUpdate();
log.info("Mic: " + user.get("mic"));
log.info("Removed: " + removed);
log.info("Updated: " + updated);
}
}
The problem arises when I try to call the muteUser method. Red5 says that the stream is closed. I think this happens when I remove the attribute of the user and added it again but I couldn't find another way to update the sharedObject's mic value.
Does any one have a better idea to update a sharedObject without losing stream?

The SO that you're requesting doesn't work like a user map that it would appear you think it does in your example. I would suggest storing a map in the SO and then do a get / add to the map; the map in this case being shared, so you'd have to make it thread-safe; I'd use a ConcurrentMap there like so:
ISharedObject so = getSharedObject(scope, "users_so");
if (so == null) {
// make sure your so exists
}
so.beginUpdate();
ConcurrentMap<String, Object> users = (ConcurrentMap<String, Object>) so.getAttribute("users");
if (users == null) {
users = new ConcurrentHashMap<String, Object>();
so.setAttribute("users" users);
}
Object user = users.get(userID);
user.put("mic", 0);
so.endUpdate();

Related

How to "SET IDENTITY_INSERT ON" on Entity Framework [duplicate]

This question already has an answer here:
Entity Framework: Cannot insert explicit value for identity column in table '[table]' when IDENTITY_INSERT is set to OFF
(1 answer)
Closed 8 months ago.
I made a few tables in EF and entered in some seed data where I give value to a few columns with a primary key. When I run the application I am getting the error message:
Cannot insert explicit value for identity column in table 'Persons' when IDENTITY_INSERT is set to OFF.
How do I turn it on? I read on here to use:
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
above the property that is a primary key. I am still getting the same error message unfortunately. Please help.
I added [DatabaseGenerated(DatabaseGeneratedOption.None)] to all my properties that have a primary key. When I ran the migration I can see that the identity column is removed, But I am still getting the same error message.
When I go into SQL SEO I can still see the identity column on my primary key. I tried refreshing the database. What am I doing wrong? The only thing I can do is go into properties and remove the identity, but why can't I do it the way mentioned above?
In EF Core 1.1.2, I got this to work with transactions. In my "database initializer" that put seed data into the tables. I used the technique from this EF6 answer. Here's a sample of the code:
using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
var user = new User {Id = 123, Name = "Joe"};
db.Users.Add(user);
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT MyDB.Users ON;");
db.SaveChanges();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT MyDB.Users OFF");
transaction.Commit();
}
Had to deal with the same issue and this seems to be a clean solution.
Credit to >> https://github.com/dotnet/efcore/issues/11586
I have made some changes so it now works with .Net Core 3.1 + (Tested in .Net 5) and also added this Method SaveChangesWithIdentityInsert
public static class IdentityHelpers
{
public static Task EnableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, enable: true);
public static Task DisableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, enable: false);
private static Task SetIdentityInsert<T>(DbContext context, bool enable)
{
var entityType = context.Model.FindEntityType(typeof(T));
var value = enable ? "ON" : "OFF";
return context.Database.ExecuteSqlRawAsync(
$"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
}
public static void SaveChangesWithIdentityInsert<T>(this DbContext context)
{
using var transaction = context.Database.BeginTransaction();
context.EnableIdentityInsert<T>();
context.SaveChanges();
context.DisableIdentityInsert<T>();
transaction.Commit();
}
}
Usage
var data = new MyType{SomeProp= DateTime.Now, Id = 1};
context.MyType.Add(data);
context.SaveChangesWithIdentityInsert<MyType>();
Improved solution based on NinjaCross' answer.
This code is added directly in the database context class and allows to save changes by also specifying that identity insert is needed for a certain type (mapped to a table).
Currently, I have only used this for integrative testing.
public async Task<int> SaveChangesWithIdentityInsertAsync<TEnt>(CancellationToken token = default)
{
await using var transaction = await Database.BeginTransactionAsync(token);
await SetIdentityInsertAsync<TEnt>(true, token);
int ret = await SaveChangesExAsync(token);
await SetIdentityInsertAsync<TEnt>(false, token);
await transaction.CommitAsync(token);
return ret;
}
private async Task SetIdentityInsertAsync<TEnt>(bool enable, CancellationToken token)
{
var entityType = Model.FindEntityType(typeof(TEnt));
var value = enable ? "ON" : "OFF";
string query = $"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}";
await Database.ExecuteSqlRawAsync(query, token);
}
Steve Nyholm's answer works fine, but I will provide some extra explanation and some generic code with exception handling.
Normally the context takes care of the transaction, but in this case manually taking care of it is required. Why?
Database context will generate a BEGIN TRAN after the SET IDENTITY_INSERT is issued. This will make transaction's inserts to fail since IDENTITY_INSERT seems to affect tables at session/transaction level.
So, everything must be wrapped in a single transaction to work properly.
Here is some useful code to seed at key level (as opposed to table level):
Extensions.cs
[Pure]
public static bool Exists<T>(this DbSet<T> dbSet, params object[] keyValues) where T : class
{
return dbSet.Find(keyValues) != null;
}
public static void AddIfNotExists<T>(this DbSet<T> dbSet, T entity, params object[] keyValues) where T: class
{
if (!dbSet.Exists(keyValues))
dbSet.Add(entity);
}
DbInitializer.cs
(assumes that model class name is the same as table name)
private static void ExecuteWithIdentityInsertRemoval<TModel>(AspCoreTestContext context, Action<AspCoreTestContext> act) where TModel: class
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT " + typeof(TModel).Name + " ON;");
context.SaveChanges();
act(context);
context.SaveChanges();
transaction.Commit();
}
catch(Exception)
{
transaction.Rollback();
throw;
}
finally
{
context.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT " + typeof(TModel).Name + " OFF;");
context.SaveChanges();
}
}
}
public static void Seed(AspCoreTestContext context)
{
ExecuteWithIdentityInsertRemoval<TestModel>(context, ctx =>
{
ctx.TestModel.AddIfNotExists(new TestModel { TestModelId = 1, ModelCode = "Test model #1" }, 1);
ctx.TestModel.AddIfNotExists(new TestModel { TestModelId = 2, ModelCode = "Test model #2" }, 2);
});
}
The solution proposed by #sanm2009 contains some nice ideas.
However the implementation has some imperfections related to the misusage of Task/async/await.
The method SaveChangesWithIdentityInsert does not return Task, nor await for the calls to EnableIdentityInsert and DisableIdentityInsert.
This could lead to undesired side effects.
The following implementations supports both async/await, and non-awaitable paradigms.
#region IDENTITY_INSERT
public static void EnableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, true);
public static void DisableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, false);
private static void SetIdentityInsert<T>([NotNull] DbContext context, bool enable)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var entityType = context.Model.FindEntityType(typeof(T));
var value = enable ? "ON" : "OFF";
context.Database.ExecuteSqlRaw($"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
}
public static void SaveChangesWithIdentityInsert<T>([NotNull] this DbContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
using var transaction = context.Database.BeginTransaction();
context.EnableIdentityInsert<T>();
context.SaveChanges();
context.DisableIdentityInsert<T>();
transaction.Commit();
}
#endregion
#region IDENTITY_INSERT ASYNC
public static async Task EnableIdentityInsertAsync<T>(this DbContext context) => await SetIdentityInsertAsync<T>(context, true);
public static async Task DisableIdentityInsertAsync<T>(this DbContext context) => await SetIdentityInsertAsync<T>(context, false);
private static async Task SetIdentityInsertAsync<T>([NotNull] DbContext context, bool enable)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var entityType = context.Model.FindEntityType(typeof(T));
var value = enable ? "ON" : "OFF";
await context.Database.ExecuteSqlRawAsync($"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
}
public static async Task SaveChangesWithIdentityInsertAsync<T>([NotNull] this DbContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
await using var transaction = await context.Database.BeginTransactionAsync();
await context.EnableIdentityInsertAsync<T>();
await context.SaveChangesAsync();
await context.DisableIdentityInsertAsync<T>();
await transaction.CommitAsync();
}
#endregion
#Steve Nyholm answer is OK, But in .Net core 3 ExecuteSqlCommand is Obsolete, ExecuteSqlInterpolated replacement of ExecuteSqlCommand:
using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
var user = new User {Id = 123, Name = "Joe"};
db.Users.Add(user);
db.Database.ExecuteSqlInterpolated($"SET IDENTITY_INSERT MyDB.Users ON;");
db.SaveChanges();
db.Database.ExecuteSqlInterpolated($"SET IDENTITY_INSERT MyDB.Users OFF");
transaction.Commit();
}
Another way is to explicitly open a connection then SET IDENTITY_INSERT <table> ON.
var conn = context.Database.GetDbConnection();
if (conn.State != ConnectionState.Open)
conn.Open();
context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Posts ON");
var post = new WeblogPost()
{
Id= oldPost.Pk, // <!--- explicit value to Id field
Title = oldPost.Title,
...
};
context.Posts.Add(post);
conn.Close();
Apparently once a connection has been explicitly opened before an EF request, that connection is not automatically closed by EF, so the setting is applied to the same connection context.
This is the same reason that Steve's response with transactions works as transactions keep a connection alive.
Note: you don't want to put the connection into a using statement if you plan to use the same context again later in the application/request. The connection has to exist, so the best way to clear the connection context is to .Close() it, thereby returning EF to its default behavior of opening and closing the connection per operation.
Below solution worked for me.(Link)
I have added below annotations. and removed [Key] Annotation.
[KeyAttribute()]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
Namespace can be changed according to the entity framework version. For Entity framework core namespace is System.ComponentModel.DataAnnotations.Schema
I did not face a data migration since I have tried in a new project.
Another way is to use ExecuteSqlRaw. Unlike ExecuteSqlInterpolated, you do not have to convert your passed string to a formattable string type.
using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
var user = new User {Id = 123, Name = "Joe"};
db.Users.Add(user);
db.Database.ExecuteSqlRaw("SET IDENTITY_INSERT MyDB.Users ON");
db.SaveChanges();
db.Database.ExecuteSqlRaw("SET IDENTITY_INSERT MyDB.Users OFF");
transaction.Commit();
}
In order to add related entities with an object graph using the DbContext I used a DbCommandInterceptor which automatically sets INSERT_IDENTITY ON for the table in question and then OFF after the insert. This works with IDs manually set and DbContext.SaveChanges. I used it in my integration tests but after a performance optimization maybe it could be suitable for production code in some cases. Here is my answer to a similar SO question which explains the details.
Use "SET IDENTITY_INSERT [table] ON/OFF" into transaction
public static void TranslateDatabase(ref BDVContext bdvContext)
{
bdvContext.Foro.RemoveRange(bdvContext.Foro);
bdvContext.SaveChanges();
using (var transaction = bdvContext.Database.BeginTransaction())
{
bdvContext.Database.ExecuteSqlRaw("SET IDENTITY_INSERT [dbo].[Foro] On");
using (old_balsaContext db = new old_balsaContext())
{
long id = 0;
foreach (ForoA77 post in db.ForoA77.Where(x => x.Fecha > new DateTime(2000,1,1) & x.IdPadre == 0 ) )
{
bdvContext.Foro.Add(new Foro
{
Id = ++id
, ParentId = 0
, EditId = 0
, IdDomains = 2
, UserNick = post.IdUsuario == 1 ? bdvContext.Users.Where(x => x.Id == 2).Single().User : post.Nick?? ""
, IdUsers = post.IdUsuario == 1 ? (int?)2 : null
, Title = post.Asunto?? ""
, Text = post.Texto?? ""
, Closed = post.Cerrado?? false
, Banned = post.Veto?? false
, Remarqued = post.Remarcado?? false
, Deleted = false
, Date = post.Fecha?? new DateTime(2001,1,1)
});
}
}
bdvContext.SaveChanges();
bdvContext.Database.ExecuteSqlRaw("SET IDENTITY_INSERT [dbo].[Foro] Off");
transaction.Commit();
}
}
Note, my entityframework was generated by reverse engineering
If you don't want to use EF core's auto-generating primary key values feature, you can turn it off. You can add your data to the primary key It should resolve the error - Set Identity Insert off
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int StudentId { get; set; }
Setting Database Generation option to None helped me. You can find more about it here- https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations
You should keep it off, dont turn it on !
Its turned off for good reasons (security performance)...
Do this instead in your code.
For example, outside your default Create controller when you need to _context.add() a new entry in your DB:
object mytable = new Mytable
{
//as in your model but without key field ! (its readonly)
myvar = "something",
myage = 50,
done = somefunctionToRetrieveData(somevar),
date = system.datetime.now(),
universalAnswer = 42
}
_context.Add(mytable);
await _context.SaveChangesAsync();

Old ASP.NET code works on one computer, not on another?

So in my global.asax, I've got the following code:
Inventory.BusinessTier bt = new Inventory.BusinessTier();
string UserLogin = bt.ExtractLogin (Request.ServerVariables ["AUTH_USER"]);
Inventory.User myUser = new Inventory.User (UserLogin);
Session ["User"] = myUser;
It works just fine on one development PC, but using the same version of Visual Studio, it craps out on the third line with this error:
System.TypeInitializationException: 'The type initializer for
'Inventory.DataTier' threw an exception.'
Inner Exception
NullReferenceException: Object reference not set to an instance of an
object.
Other than a line adding impersonation in my web.config (it has to be there now), I haven't changed a single thing. Is there a way to get more info on this? I can't even trace it, because if I put a debug line in the User object constructor, it never hits it. I'm at a bit of a loss. Would appreciate any advice.
EDIT to answer questions below:
InventoryUser is a very simple user object that reads the current from the database and stores some basic user info in properties, such as UserID, Role, RoleID, and IsAdmin.
The DataTier class is a class that interacts with the database. It is used in multiple projects, so I'm quite sure it's not the problem. I tried to paste in the code anyway, but it exceeded the limit for a post.
I'm reasonably sure the problem is related to the user class. It's short, so I can paste it in here:
using System;
using System.Data;
// This is the user business object. It contains information pertaining to the current user of the application. Notably, it
// contains the department ID, which determines what inventory items the user will see when using the application. Only
// specified employees with admin access can see all items for all departments, and that is determined by a specific department ID.
namespace Inventory {
public class User {
private Guid _UserID;
private Guid _RoleID;
private Guid _UserDepartmentID;
private string _UserRole = "";
private string _UserName = "";
private bool _IsAuthorizedUser = false;
private bool _IsAdmin = false;
// Attribute declarations
public Guid UserID {
get {
return _UserID;
}
set {
_UserID = value;
}
}
public string UserRole {
get {
return _UserRole;
}
set {
_UserRole = value;
}
}
public Guid RoleID {
get {
return _RoleID;
}
set {
_RoleID = value;
}
}
public string UserName {
get {
return _UserName;
}
set {
_UserName = value;
}
}
public Guid UserDepartmentID {
get {
return _UserDepartmentID;
}
set {
_UserDepartmentID = value;
}
}
public bool IsAdmin {
get {
return _IsAdmin;
}
set {
_IsAdmin = value;
}
}
public bool IsAuthorizedUser {
get {
return _IsAuthorizedUser;
}
set {
_IsAuthorizedUser = value;
}
}
// -----------------
// - Constructor -
// -----------------
public User (string UserLogin) {
string ShortUserLogin = ExtractLogin (UserLogin);
GetUser (ShortUserLogin);
}
// ------------------
// - ExtractLogin -
// ------------------
public string ExtractLogin (string Login) {
// The domain and "\" symbol must be removed from the string, leaving only the user name.
int pos = Login.IndexOf (#"\");
return Login.Substring (pos + 1, Login.Length - pos - 1);
}
// -------------
// - GetUser -
// -------------
// This method is called to fill the user object based on the user's login. It ultimately gets authorized user data
// from the user table.
public void GetUser (string UserName) {
DataTier dt1 = new DataTier();
DataTable dt = dt1.GetUserInfo (UserName);
int RecordCount = dt.Rows.Count;
switch (RecordCount) {
case 1: // There is one user name match, as there should be. This is the likely situation.
DataRow dr = dt.Rows[0];
UserID = (Guid)dr ["UserID"];
UserRole = (string)dr ["UserRole"];
RoleID = (Guid)dr ["RoleID"];
this.UserName = UserName;
UserDepartmentID = (Guid)dr ["DepartmentID"];
IsAdmin = (bool)dr ["IsAdmin"];
IsAuthorizedUser = true;
break;
case 0: // There are no user name matches (unauthorized use).
IsAdmin = false;
IsAuthorizedUser = false;
break;
default: // There are multiple user name matches (problem!).
IsAdmin = false;
IsAuthorizedUser = false;
break;
}
}
}
}

How to display scores form Firebase

I have a quiz app and I would like to display scores on last screen, however I have an issue how to do it.
Here' how my score script looks like:
{
//Zliczanie punktów i wyświetlanie wyniku
public static int pointssum = 0;
public Text points;
private string user;
private Text scoresboard;
USers users = new USers();
void Start()
{
points = GetComponent<Text>();
Posttodb();
}
void Update()
{
points.text = "Poprawne odpowiedzi: " + pointssum;
}
private void Posttodb()
{
user = nazwagracza.Playernick;
if (user!= null)
{
USers users = new USers();
RestClient.Put("https://quizgame-inz.firebaseio.com/" + user + ".json", users);
}
}
private void Getdata()
{
RestClient.GetArray<USers>("https://quizgame-inz.firebaseio.com/.json?orderBy='scores'&startAt=0").Then(response =>
{
users = response;
});
}
}
I tried to assign this data to user value but I'm getting error cannot implicitly convert type.
Can you please help we with this?
users is of type USers .. it is not an array.
You did probably mean e.g.
// Returns an array of USers
RestClient.GetArray<USers>("https://quizgame-inz.firebaseio.com/.json?orderBy='scores'&startAt=0").Then(response =>
{
// get the first instance
users = response[0];
});
or make users of type
USers[] users;
depending on your needs.
Due to the naming and description it sounds like you would want to do the latter since you want to display all scores, not only the best one.

Firebase Query not being updated

I want my firebase database link to be updated depending on what the user keys in inside the searchview but the link is not updated unless I open another activity and jump back to it.I have attacked my code in the bottom. So how do I refresh it automatically ?
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener()
{
#Override
public boolean onQueryTextSubmit(String query) {
query = sv.getQuery().toString();
Toast.makeText(MainMenu.this,query, Toast.LENGTH_SHORT).show();
makeItem();
return true;
}
#Override
public boolean onQueryTextChange(String newText) {
return false;
}
});
public void makeItem ()
{
lv = findViewById(R.id.listView);
db = FirebaseDatabase.getInstance().getReferenceFromUrl("https://vsem-inventory.firebaseio.com/ItemList").orderByChild("ProductName").startAt(query).endAt(query+"\uf8ff");
FirebaseListOptions<ItemObject> options = new FirebaseListOptions.Builder<ItemObject>()
.setLayout(R.layout.content_main_menu_list)
.setQuery(db,ItemObject.class)
.build();
mAdapter = new FirebaseListAdapter<ItemObject>(options) {
#Override
protected void populateView(#NonNull View v, #NonNull ItemObject model, int position) {
final TextView tvAmount = v.findViewById(R.id.amount);
final TextView tvName = v.findViewById(R.id.name);
final TextView tvSerial = v.findViewById(R.id.serialNo);
final TextView tvSupplier = v.findViewById(R.id.supplierName);
final ImageView more = v.findViewById(R.id.more);
ImageView statusimg = v.findViewById(R.id.status);
Drawable paidIcon = v.getResources().getDrawable(R.drawable.succes);
Drawable lateIcon = v.getResources().getDrawable(R.drawable.late);
tvName.setText(model.getProductName());
tvSerial.setText(model.getSerialNo());
tvAmount.setText(model.getQuantity());
tvSupplier.setText(model.getModel());
final String Remarks = model.getRemarks();
final String cat = model.getCategory();
if(model.getQuantity().equals("0"))
statusimg.setImageDrawable(lateIcon);
else
statusimg.setImageDrawable(paidIcon);
more.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v) {
String serialNo = tvSerial.getText().toString();
String itemName = tvName.getText().toString();
String quan = tvAmount.getText().toString();
String supplier = tvSupplier.getText().toString();
showMenu(itemName,more,serialNo,quan,supplier,cat,Remarks);
}
});
}
};
lv.setAdapter(mAdapter);
}
The standard way is to call notifyDataSetChanged() after setting your adapter to your list view
Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.
mAdapter.notifyDataSetChanged();
Although I have seen some situations where only using this does not work and must be followed by these 2 commands.
lv.invalidateViews();
lv.scrollBy(0, 0);
And if all else comes to fail falling back on destroying and redrawing the list view might be your only viable option.
lv.destroyDrawingCache();
lv.setVisibility(ListView.INVISIBLE);
lv.setVisibility(ListView.VISIBLE);
EDIT : After looking at it a while more I just noticed you're missing listeners for your firebase. I assume you already have them somewhere as you already have the list but failing your refresh functions, what you can try is restarting the listeners whenever you're done with a query.
lv.setAdapter(mAdapter);
mAdapter.stopListening();
mAdapter.startListening();

fubumvc - simple forms validation using IFailureValidationPolicy

I've been trying to implement form validation correctly and a discussion on fubu mailing list has been the most helpful (http://groups.google.com/group/fubumvc-devel/browse_thread/thread/d54b135fe0254653/12180cd86e9dc50b).
I'm still not entirely clear on certain points, I'm a newbie so I'm going through some yak shaving.
It seems like the example given in the discussion performed the validation within the controller itself using IsValid(model).
I'm trying to avoid this by decorating my input model with validation attributes such as Required and then use the validation configuration to Transfer on failure (via a policy).
this.Validation(x => {
x.Actions
.Include(call => call.HasInput && call.InputType().Name.EndsWith("Input"));
x.Failures
.ApplyPolicy<AccountValidationFailedPolicy>();
});
And here's the class that implments the policy:
public class AccountValidationFailedPolicy : IValidationFailurePolicy {
public bool Matches(ValidationFailure context) {
return (context.InputType() == typeof (RegisterAccountInput));
}
public void Handle(ValidationFailure context) {
var incomingRequest = (RegisterAccountInput) context.InputModel;
var failedValidation = new RegisterationFailedNotification {
CVV = incomingRequest.CVV,
AcceptTerms = incomingRequest.AcceptTerms,
Countries = incomingRequest.Countries,
PhoneNumber = incomingRequest.PhoneNumber,
PIN = incomingRequest.PIN
};
FubuContinuation.TransferTo(failedValidation);
}
}
Handle simply tries to Transfer to another action via a new model, copying the values into the new model so that I can redisplay them again on the form.
I must be doing something wrong here, because it's not transferring anywhere.
I have a class with this method which I was hoping would handle it.
public AccountViewModel New(RegisterationFailedNotification notification) {
....
}
Am I on track here, or is there something fundamental that I'm not getting? Perhaps a policy is not the thing to do here?
#stantona
The policy mechanism will work here. I'll spare you the details about how I plan to make this simpler (very soon), and note that your use of FubuContinuation.TransferTo simply creates a FubuContinuation -- it doesn't execute it.
Here's what you need:
public class AccountValidationFailedPolicy : IValidationFailurePolicy {
private readonly IFubuRequest _request;
private readonly IValidationContinuationHandler _handler;
public AccountValidationFailedPolicy(IFubuRequest request, IValidationContinuationHandler handler) {
_request = request;
_handler = handler;
}
public bool Matches(ValidationFailure context) {
return (context.InputType() == typeof (RegisterAccountInput));
}
public void Handle(ValidationFailure context) {
var incomingRequest = (RegisterAccountInput) context.InputModel;
var failedValidation = new RegisterationFailedNotification {
CVV = incomingRequest.CVV,
AcceptTerms = incomingRequest.AcceptTerms,
Countries = incomingRequest.Countries,
PhoneNumber = incomingRequest.PhoneNumber,
PIN = incomingRequest.PIN
};
var continuation = FubuContinuation.TransferTo(failedValidation);
_request.Set(continuation);
_handler.Handle();
}
}

Resources